naro-cli 0.11.0 → 0.13.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 +271 -13
- package/dist/{chunk-L7JOGWP3.js → chunk-O4NJRDJE.js} +5167 -363
- package/dist/deploy.js +1 -1
- package/dist/index.js +3827 -210
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -69,6 +69,15 @@ naro logs <deployment-id> Build logs of a deployment
|
|
|
69
69
|
naro logs --type build --follow Stream build logs until READY/FAILED
|
|
70
70
|
naro logs --env preview Preview-surface runtime logs
|
|
71
71
|
|
|
72
|
+
naro insights Speed Insights: Web Vitals p75 (--metric lcp, --range 7d)
|
|
73
|
+
naro insights --route '/posts/[id]' The paths under one route
|
|
74
|
+
naro insights --env preview The same for the preview worker
|
|
75
|
+
naro insights enable | disable Turn measuring on or off
|
|
76
|
+
|
|
77
|
+
naro protection Preview protection: on or off, and whether a bypass secret is set
|
|
78
|
+
naro protection enable | disable Protect the preview URL (members only) or open it to anyone with the link (disable asks first; --yes in CI)
|
|
79
|
+
naro protection bypass [remove] Create (shown once) or remove the secret automation sends as x-naro-protection-bypass (replacing or removing asks first; --yes in CI)
|
|
80
|
+
|
|
72
81
|
naro env list [--reveal] Secrets masked unless --reveal
|
|
73
82
|
naro env add DATABASE_URL production
|
|
74
83
|
naro env remove DATABASE_URL
|
|
@@ -91,9 +100,11 @@ naro db dump --output backup.sql SQL dump, streamed from Cloudflare (stdout wi
|
|
|
91
100
|
naro db restore --bookmark <id> Time Travel, last 30 days (or --timestamp <time>);
|
|
92
101
|
asks first, prints the command that undoes it
|
|
93
102
|
naro db pull Existing database, no migrations yet: writes its schema
|
|
94
|
-
as migrations/0001_remote_schema.sql,
|
|
103
|
+
and policies as migrations/0001_remote_schema.sql,
|
|
104
|
+
recorded as applied
|
|
95
105
|
naro db reset [--dry-run] Drop every table, view and trigger, clear the ledger and
|
|
96
|
-
apply ./migrations again (asks first;
|
|
106
|
+
the policies, and apply ./migrations again (asks first;
|
|
107
|
+
--yes in CI; refuses files the server would refuse)
|
|
97
108
|
naro db lint [--strict] Check the schema for what D1 actually trips on; exits 1
|
|
98
109
|
on an error, and on a warning too with --strict
|
|
99
110
|
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
|
|
@@ -158,6 +169,18 @@ naro policy set posts --from-file policy.json
|
|
|
158
169
|
Shorthand: writes the policies naro_select … naro_delete
|
|
159
170
|
naro policy remove todos Drop every policy on the table: back to deny (asks first)
|
|
160
171
|
|
|
172
|
+
naro storage Naro Storage: on or off, limits, usage, buckets
|
|
173
|
+
naro storage enable Turn it on: the project's R2 bucket, bound to the backend
|
|
174
|
+
naro storage bucket create avatars --public --file-size-limit 5MiB --allowed-mime-types image/png,image/jpeg
|
|
175
|
+
naro storage bucket update avatars --private | --file-size-limit none | --allowed-mime-types any
|
|
176
|
+
naro storage bucket update docs --public (asks first: anyone with an object's URL can download it)
|
|
177
|
+
naro storage bucket list | empty <name> | remove <name> [--empty] (empty and remove ask first)
|
|
178
|
+
naro storage ls naro://avatars/u1 [--recursive]
|
|
179
|
+
naro storage cp ./me.png naro://avatars/u1/me.png [--upsert] Up to 50 MiB, with the service key
|
|
180
|
+
naro storage cp naro://docs/team-1/plan.pdf ./ Replaces a local file of the same name
|
|
181
|
+
naro storage rm naro://avatars/u1/old.png [--recursive] (asks first)
|
|
182
|
+
naro storage rm naro://avatars/u1/ --recursive Everything under u1/; written u1, an object u1 too
|
|
183
|
+
|
|
161
184
|
naro promote <deployment-id> Publish a build to production
|
|
162
185
|
naro rollback [deployment-id] Back to the previous production build
|
|
163
186
|
```
|
|
@@ -191,12 +214,43 @@ reads the bookmark from just before the drops and prints it, so
|
|
|
191
214
|
`naro db restore --bookmark <that>` puts the data back. `--dry-run` lists what
|
|
192
215
|
it would drop and re-apply without touching anything.
|
|
193
216
|
|
|
217
|
+
The backend's accounts, sessions, settings and the other `_naro_` tables are
|
|
218
|
+
kept — except its policies: they are cleared with the schema and the migration
|
|
219
|
+
files create them again. Before it reads anything, `db reset` checks the files
|
|
220
|
+
as `naro db push --dry-run` does: when the control plane would refuse one, the
|
|
221
|
+
re-apply would stop there with the schema already dropped, so the reset is
|
|
222
|
+
refused with `VALIDATION_FAILED`, each file as `<file>: Statement <i> of <n>:
|
|
223
|
+
…` (`--dry-run` prints them and exits 1). A policy that exists only in the
|
|
224
|
+
database would be lost — one made with `naro policy create` or
|
|
225
|
+
`naro policy set`, and the owner policy that `naro db table create --owner`,
|
|
226
|
+
MCP's `create_table` and the table editor add, which no migration file holds.
|
|
227
|
+
So before it asks, `db reset` lists the policies your files do not make, each
|
|
228
|
+
with the SQL that makes it again — its `CREATE POLICY` and, for an owner rule,
|
|
229
|
+
the `COMMENT ON POLICY … IS 'naro:owner <column>'` that keeps its column
|
|
230
|
+
(`--json`: `policiesNotInFiles[].sql`); add it to a migration first to keep
|
|
231
|
+
them. The policies of a table your files do not create go with that table and
|
|
232
|
+
are named apart, with no SQL (`--json`: `policiesOfTablesNotInFiles`): a
|
|
233
|
+
migration that made them would stop at a table that is not there. A file the
|
|
234
|
+
re-apply would stop at that the check above does not catch (one whose SQL
|
|
235
|
+
cannot be split) is named instead — `<file> would be refused: <reason>`
|
|
236
|
+
(`--json`: `wouldBeRefused`) — and then no policy is called lost. A policy row
|
|
237
|
+
that cannot be read is listed as a `-- Policy … cannot be read` comment
|
|
238
|
+
instead: copying it keeps nothing, so drop that policy and create it again. A
|
|
239
|
+
backend whose worker predates named policies is refused with
|
|
240
|
+
`409 BACKEND_OUTDATED`: run `naro backend enable` first.
|
|
241
|
+
|
|
194
242
|
`naro db lint` reads the schema and runs the checks D1 allows. Its rules are
|
|
195
243
|
the ones D1 actually trips on — orphaned rows, a failed `quick_check`, a
|
|
196
244
|
missing or nullable primary key, a foreign key no index starts with, a trigger
|
|
197
245
|
the REST API cannot recreate, a virtual table that makes `db dump` refuse the
|
|
198
|
-
whole database.
|
|
199
|
-
|
|
246
|
+
whole database. On a project with a backend it also checks the policies:
|
|
247
|
+
`rls_no_policy` (a table the Data API refuses entirely),
|
|
248
|
+
`rls_policy_text_default` (a number column a policy reads as a boolean whose
|
|
249
|
+
`DEFAULT` is text, such as `BOOLEAN DEFAULT 'true'`) and
|
|
250
|
+
`rls_policy_unindexed` (warnings), and `rls_policy_invalid`, a policy the
|
|
251
|
+
backend cannot apply — one that reads a text column as true or false among
|
|
252
|
+
them (an error). There is no "unused index" guess: D1 keeps
|
|
253
|
+
no execution statistics to base one on. An error exits 1, a warning exits 0 — `--strict`
|
|
200
254
|
makes warnings fail too, so CI can gate on it:
|
|
201
255
|
|
|
202
256
|
```bash
|
|
@@ -217,7 +271,9 @@ from a fixed list — and applies it as one migration, recorded in
|
|
|
217
271
|
`d1_migrations` and written to `./migrations` as `NNNN_schema_<op>_<table>.sql`,
|
|
218
272
|
numbered after your local files and the ledger. `naro migration list`,
|
|
219
273
|
`db pull` and `db reset` see it like any other migration; with `--no-write`
|
|
220
|
-
there is no file, so `db reset` will not re-apply it.
|
|
274
|
+
there is no file, so `db reset` will not re-apply it. The owner policy
|
|
275
|
+
`--owner` adds is not in that file either: `db reset` lists it, with its SQL,
|
|
276
|
+
before it removes it — add that SQL to a migration to keep it. `--dry-run` prints the
|
|
221
277
|
SQL and changes nothing. While a local migration is not in the ledger yet, a
|
|
222
278
|
change is refused — `naro db push` it first, or the change would run before it.
|
|
223
279
|
|
|
@@ -233,15 +289,17 @@ Every primary key but a single `integer` one is `NOT NULL`; a foreign key must
|
|
|
233
289
|
point at its table's primary key or a unique column. `--owner user_id` adds
|
|
234
290
|
`user_id TEXT NOT NULL` and, when the backend is on and ready, an owner policy for all
|
|
235
291
|
four actions; any other new table is closed to the Data API until you give it
|
|
236
|
-
a policy (`
|
|
292
|
+
a policy (`CREATE POLICY` in a migration, or `naro policy create` or
|
|
293
|
+
`naro policy set`; the command says so).
|
|
237
294
|
Policies are kept by table name: a drop removes the table's policies whenever
|
|
238
295
|
the project has a backend, on or off (the command says so). A rename leaves
|
|
239
296
|
them under the old name, where they apply to no table — the command prints
|
|
240
297
|
what to run to move them. Move them then: the next time `naro db table`
|
|
241
298
|
creates a table as that name or renames one to it, renaming the table back
|
|
242
299
|
included, the policies stored under the name are removed first, so a new
|
|
243
|
-
table never inherits an earlier one's (
|
|
244
|
-
`naro db
|
|
300
|
+
table never inherits an earlier one's (except, on a project without a
|
|
301
|
+
backend, one changed through `naro db table`, the table editor or MCP) — and
|
|
302
|
+
neither does a table made by `naro db push` or `naro db query`. Whatever a command removes it prints
|
|
245
303
|
so it can be made again: as `CREATE POLICY` statements, which
|
|
246
304
|
`naro policy create` takes back, and — for owner rules, whose owner column
|
|
247
305
|
`CREATE POLICY` cannot say — as the `naro policy set <table> … --owner <column>`
|
|
@@ -439,8 +497,112 @@ project's backend, then run the command again.
|
|
|
439
497
|
|
|
440
498
|
Policies are Postgres's row-level security, as in Supabase. A table without a
|
|
441
499
|
policy refuses every anon and signed-in request; the service key skips
|
|
442
|
-
policies. Write
|
|
443
|
-
|
|
500
|
+
policies. Write a table's policies in the migration that creates it —
|
|
501
|
+
`CREATE POLICY` after the `CREATE TABLE`, in the same file. `naro db push`,
|
|
502
|
+
`naro migration up`, `naro db query` and the MCP tools `apply_migration` and
|
|
503
|
+
`execute_sql` apply them together with the table, all or nothing. Three shapes
|
|
504
|
+
cover most apps:
|
|
505
|
+
|
|
506
|
+
```sql
|
|
507
|
+
-- migrations/0002_todos.sql: each signed-in user reads and changes their own rows.
|
|
508
|
+
CREATE TABLE todos (
|
|
509
|
+
id integer PRIMARY KEY,
|
|
510
|
+
user_id uuid NOT NULL,
|
|
511
|
+
title text NOT NULL
|
|
512
|
+
);
|
|
513
|
+
CREATE POLICY "own rows" ON todos FOR ALL TO authenticated
|
|
514
|
+
USING ((select auth.uid()) = user_id)
|
|
515
|
+
WITH CHECK ((select auth.uid()) = user_id);
|
|
516
|
+
|
|
517
|
+
-- migrations/0003_posts.sql: anyone reads, signed in or not.
|
|
518
|
+
CREATE TABLE posts (id integer PRIMARY KEY, title text NOT NULL);
|
|
519
|
+
CREATE POLICY "read" ON posts FOR SELECT TO anon, authenticated USING (true);
|
|
520
|
+
|
|
521
|
+
-- migrations/0004_reports.sql: an app only its admins sign in to. First run
|
|
522
|
+
-- naro backend auth signups off and create the admin accounts: authenticated
|
|
523
|
+
-- then means exactly those accounts.
|
|
524
|
+
CREATE TABLE reports (id integer PRIMARY KEY, body text NOT NULL);
|
|
525
|
+
CREATE POLICY "admins" ON reports FOR ALL TO authenticated USING (true) WITH CHECK (true);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
A migration or a query takes `CREATE POLICY`; `DROP POLICY [IF EXISTS]` (`IF
|
|
529
|
+
EXISTS` skips a missing table too, as in Postgres); `COMMENT ON POLICY p ON t
|
|
530
|
+
IS 'naro:owner <column>'`, which makes `p` the owner rule `naro policy set
|
|
531
|
+
--owner` writes (any other comment is ignored); and `ALTER TABLE t ENABLE |
|
|
532
|
+
FORCE | NO FORCE ROW LEVEL SECURITY`, which changes nothing because
|
|
533
|
+
row-level security is always on. Before anything runs, naro refuses these with
|
|
534
|
+
`400 VALIDATION_FAILED` "Statement <i> of <n>: …":
|
|
535
|
+
|
|
536
|
+
- `ALTER TABLE … DISABLE ROW LEVEL SECURITY` — it is always on; to let
|
|
537
|
+
everyone through, create a policy: `CREATE POLICY "open" ON t USING (true)
|
|
538
|
+
WITH CHECK (true);`
|
|
539
|
+
- `GRANT` and `REVOKE` — every table is behind row-level security, so write a
|
|
540
|
+
policy instead;
|
|
541
|
+
- `ALTER POLICY` — `DROP POLICY` and `CREATE POLICY` again;
|
|
542
|
+
- a table named `public.x` in `CREATE TABLE`, `DROP TABLE` or a rename: `SQLite
|
|
543
|
+
has no schema "public": write the table name without "public."`
|
|
544
|
+
- a `CREATE POLICY` that reads a column inside a subquery with no table in
|
|
545
|
+
front of it, when a later statement of the same file creates, drops, renames
|
|
546
|
+
or alters the policy's table or a table its subqueries read: `a later
|
|
547
|
+
statement changes a table, so name the table of every column inside this
|
|
548
|
+
policy's subqueries (for example members.user_id).` Postgres decides whose
|
|
549
|
+
column it is when the policy is created; naro binds it once the file is in,
|
|
550
|
+
when the later statement may have added that name to the subquery's table.
|
|
551
|
+
A later statement that changes only other tables — the next table of a file
|
|
552
|
+
grouped table by table — leaves the policy alone.
|
|
553
|
+
|
|
554
|
+
The statements of one request apply all-or-nothing, the policies with the
|
|
555
|
+
tables: a policy on a table that does not exist
|
|
556
|
+
(`relation "nope" does not exist`), a name already taken, or a 33rd policy on
|
|
557
|
+
one table fails the whole request with `400 DATABASE_QUERY_FAILED`, names the
|
|
558
|
+
statement, and applies nothing — not even the `CREATE TABLE` before it. Policy
|
|
559
|
+
statements, and statements that create, drop or rename a table, take no
|
|
560
|
+
`params` (`400 VALIDATION_FAILED`): write the values into the SQL.
|
|
561
|
+
|
|
562
|
+
Policies go with their table, as in Postgres: `DROP TABLE` drops them, and a
|
|
563
|
+
new table never inherits the leftovers of an earlier one with the same name
|
|
564
|
+
(except tables changed through the table editor on a project without a backend).
|
|
565
|
+
SQLite changes a column by building the table again — `CREATE TABLE
|
|
566
|
+
__new_x`, copy the rows, `DROP TABLE x`, `ALTER TABLE __new_x RENAME TO x` — so
|
|
567
|
+
the drop removes `x`'s policies and the rename does not bring them back. Write
|
|
568
|
+
the `CREATE POLICY` statements again after the rename, in the same migration.
|
|
569
|
+
A plain `ALTER TABLE a RENAME TO b` does not move them either: they stay under
|
|
570
|
+
`a`, and `b` has none until you create them again.
|
|
571
|
+
The answer's `warnings` lists the policies a migration removed, with SQL you
|
|
572
|
+
can paste; it also says when the project has no backend yet (the policies are
|
|
573
|
+
saved and apply once `naro backend enable` turns it on) and when a policy was
|
|
574
|
+
applied that the Data API refuses, with the reason. `naro db push` and
|
|
575
|
+
`naro db query` print each warning as `⚠ …` on stderr (`--json` has it whole
|
|
576
|
+
in `warnings`, a query's in `result.warnings`).
|
|
577
|
+
|
|
578
|
+
`naro db push --dry-run` parses the policy statements of the pending files on
|
|
579
|
+
your machine and prints what it would refuse as `<file>: Statement <i> of <n>:
|
|
580
|
+
…` (exit 1); whether a table or a policy exists is known only when the file is
|
|
581
|
+
applied. `naro db pull` writes the policies that exist at the end of the
|
|
582
|
+
baseline file — `ALTER TABLE … ENABLE ROW LEVEL SECURITY;` for each table,
|
|
583
|
+
then each `CREATE POLICY …;`, an owner rule's column as `COMMENT ON POLICY …
|
|
584
|
+
IS 'naro:owner <column>';` (a database without policies pulls as plain
|
|
585
|
+
SQLite). `naro db reset` clears the policies with the schema and creates them
|
|
586
|
+
again from the migration files, so a policy that exists only in the database
|
|
587
|
+
is lost; it lists those before it asks, each with the SQL to add to a
|
|
588
|
+
migration to keep it (an owner rule's column as its `COMMENT ON POLICY`; a
|
|
589
|
+
row that cannot be read is only a comment — drop it and create it again), and
|
|
590
|
+
names apart, without SQL, the policies of tables your files do not create. It
|
|
591
|
+
refuses to start when the control plane would refuse one of the files.
|
|
592
|
+
`naro db lint`, on a
|
|
593
|
+
project with a backend, reports `rls_no_policy` (a table the Data API refuses
|
|
594
|
+
entirely), `rls_policy_text_default` and `rls_policy_unindexed` as warnings,
|
|
595
|
+
and `rls_policy_invalid` — a
|
|
596
|
+
policy the backend cannot apply, such as one that reads a text column as true
|
|
597
|
+
or false — as an error.
|
|
598
|
+
|
|
599
|
+
`naro policy create` sends the same statements to the database directly: one
|
|
600
|
+
in quotes, or `--file` with several, `DROP POLICY` included. Nothing records
|
|
601
|
+
them in `./migrations`, so `naro db reset` removes what your files do not
|
|
602
|
+
make; use it for a table that already exists. A change is live within about 10
|
|
603
|
+
seconds, and `naro policy list` prints the policies back as SQL. More complete
|
|
604
|
+
policies — they give each user their own todos, everyone the published posts
|
|
605
|
+
and team members their team's docs, and hide deleted docs from all of them:
|
|
444
606
|
|
|
445
607
|
```sql
|
|
446
608
|
-- Each signed-in user reads and changes their own todos.
|
|
@@ -560,7 +722,9 @@ policy — `naro policy create`, `naro policy drop` and `naro policy set` — wi
|
|
|
560
722
|
`naro backend enable` uploads the current backend and turns the stored rules
|
|
561
723
|
into named policies; it also turns a backend that is off back on, so run
|
|
562
724
|
`naro backend disable` again afterwards if it should stay off. `naro policy
|
|
563
|
-
list` and `naro policy remove` work in the meantime.
|
|
725
|
+
list` and `naro policy remove` work in the meantime. A migration or a query
|
|
726
|
+
that holds a `CREATE POLICY` or `DROP POLICY` gets the same answer before
|
|
727
|
+
anything runs; SQL without one runs as before.
|
|
564
728
|
|
|
565
729
|
`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
730
|
|
|
@@ -584,7 +748,21 @@ pass — reads a row as it will be stored and compares it with the column's
|
|
|
584
748
|
collation (`COLLATE NOCASE`, `RTRIM`). So:
|
|
585
749
|
|
|
586
750
|
- a number column it reads takes numbers, booleans and null, not strings, and
|
|
587
|
-
cannot be compared with `like` or `ilike
|
|
751
|
+
cannot be compared with `like` or `ilike` — the Data API refuses a string
|
|
752
|
+
for a column that any policy of any table reads (an owner rule's own id
|
|
753
|
+
column excepted) with `400 INVALID_QUERY`. For a column whose declared type
|
|
754
|
+
has `INT`, `REAL`, `FLOA`, `DOUB`, `NUM`, `DEC`, `BOOL` or `BIT` in it, that
|
|
755
|
+
is every string — and even when no policy reads it, a string SQLite would
|
|
756
|
+
keep as text (`'abc'`, `'true'`; `'7'` is stored as 7), which Postgres
|
|
757
|
+
refuses as a type error and a policy written later would read inside out. A
|
|
758
|
+
column declared another type (`uuid`, `date`, `timestamptz`, `json`) keeps a
|
|
759
|
+
string as text, so Supabase's `user_id uuid` with `auth.uid() = user_id`
|
|
760
|
+
works; there the Data API refuses only a string SQLite would store as a
|
|
761
|
+
number (`'12'`), a string with a NUL character, and any string when a
|
|
762
|
+
policy reads the column as true or false or compares it with a number. On such a
|
|
763
|
+
column a `WITH CHECK` that orders it against a number (`<`, `>`, `BETWEEN`)
|
|
764
|
+
can judge a new text differently from the row it becomes, and `LIKE` or a
|
|
765
|
+
text function on it is refused as on a number column;
|
|
588
766
|
- a primary key column it reads must be sent, and not as null;
|
|
589
767
|
- any other column it reads may be left out only when it has no default or a
|
|
590
768
|
plain one — `NULL`, `TRUE`, `FALSE`, a number, or a quoted string on a column
|
|
@@ -609,6 +787,83 @@ An `--update-check` you give replaces the default: without
|
|
|
609
787
|
`user_id`. Write
|
|
610
788
|
`--update-when 'user_id.eq.auth.uid()' --update-check 'user_id.eq.auth.uid(),status.eq.draft'`.
|
|
611
789
|
|
|
790
|
+
## Release notes
|
|
791
|
+
|
|
792
|
+
### 0.13.0
|
|
793
|
+
|
|
794
|
+
- `naro insights` shows the project's Speed Insights from the terminal: each
|
|
795
|
+
Web Vital's p75 with its good, needs-improvement and poor shares, then the
|
|
796
|
+
selected metric (`--metric`, `--range`) by page, device, the elements
|
|
797
|
+
behind slow visits and deployment. Production by default; `--env preview`
|
|
798
|
+
reads the preview worker. `--route '/posts/[id]'` lists the paths under
|
|
799
|
+
one route, `--json` prints the whole report, and
|
|
800
|
+
`naro insights enable | disable` turns measuring on or off for production
|
|
801
|
+
and preview together (one switch, so `--env` is refused there).
|
|
802
|
+
- `naro protection` shows whether the project's preview URL is protected and
|
|
803
|
+
whether a bypass secret is set. Protection is on by default: only members
|
|
804
|
+
of the project can open `<name>-preview.naro.sh`, after signing in to naro.
|
|
805
|
+
A GET or HEAD that is a page navigation (`Sec-Fetch-Mode: navigate`), or that
|
|
806
|
+
sends no `Sec-Fetch-Mode` and an `Accept` including `text/html`, is
|
|
807
|
+
redirected to the naro sign-in; every other request gets
|
|
808
|
+
`401 PREVIEW_PROTECTED`. A request the browser sends without cookies (a
|
|
809
|
+
`<link rel="manifest">` without `crossorigin="use-credentials"`) gets 401
|
|
810
|
+
too.
|
|
811
|
+
`naro protection enable` and `naro protection disable` switch it (the edge
|
|
812
|
+
picks the change up within about a minute). `naro protection bypass`
|
|
813
|
+
creates the secret CI or a monitor sends as the `x-naro-protection-bypass`
|
|
814
|
+
header (shown once; it replaces any earlier one) and
|
|
815
|
+
`naro protection bypass remove` deletes it. `disable`, a `bypass` that
|
|
816
|
+
replaces a secret and `bypass remove` ask first; without a terminal they need
|
|
817
|
+
`--yes` and exit 1 with `CONFIRMATION_REQUIRED` otherwise, nothing written.
|
|
818
|
+
The Naro backend API (`/_naro/v1`) and production are never protected.
|
|
819
|
+
- `naro storage` manages the project's Naro Storage: `enable` turns it on,
|
|
820
|
+
`bucket list | create | update | empty | remove` manage the buckets
|
|
821
|
+
(public or private, the largest file, the types taken), and `ls`, `cp`
|
|
822
|
+
and `rm` work on `naro://<bucket>/<path>` with the project's service key
|
|
823
|
+
(`NARO_SERVICE_KEY`, or fetched with a warning) — the bytes go to the
|
|
824
|
+
project's backend, not through naro. One upload is 50 MiB at most.
|
|
825
|
+
`bucket update --public`, `empty`, `remove` and `rm` ask first; without a
|
|
826
|
+
terminal they need `--yes` (`CONFIRMATION_REQUIRED` otherwise, nothing
|
|
827
|
+
sent).
|
|
828
|
+
- `naro policy create` and `naro policy set` take `storage.objects`, as
|
|
829
|
+
migrations already did. A backend from before Storage answers
|
|
830
|
+
`409 BACKEND_OUTDATED`.
|
|
831
|
+
- `naro db push --dry-run`, `naro db reset` and `naro db lint` read
|
|
832
|
+
`CREATE POLICY … ON storage.objects`. 0.12.0 refused such a file on this
|
|
833
|
+
machine (`schema storage does not exist`), and its `db lint` and
|
|
834
|
+
`db reset` did not know Storage's policies.
|
|
835
|
+
|
|
836
|
+
### 0.12.0
|
|
837
|
+
|
|
838
|
+
- Migrations and `naro db query` take Postgres's `CREATE POLICY`,
|
|
839
|
+
`DROP POLICY` and `COMMENT ON POLICY … IS 'naro:owner <column>'` next to
|
|
840
|
+
the tables they are for (see Row-level security). `naro db reset` clears
|
|
841
|
+
the policies with the schema and creates them again from your migration
|
|
842
|
+
files; before it drops anything it checks the files as
|
|
843
|
+
`naro db push --dry-run` does, and lists the policies your files do not
|
|
844
|
+
make, with the SQL to keep them.
|
|
845
|
+
- A failed `db push` keeps the server's `details.warnings` of the file that
|
|
846
|
+
failed (the policies a table rebuild took, with their SQL) and prints them
|
|
847
|
+
as `⚠` lines; the warnings of the files before it are
|
|
848
|
+
`details.appliedWarnings`.
|
|
849
|
+
- **Update the CLI before you run `naro db reset`.** `naro-cli` 0.11.0
|
|
850
|
+
(published on 2026-10-05 from main `98bf538b`, PR #82) and every older
|
|
851
|
+
version know nothing of policies in migrations. On a project whose backend
|
|
852
|
+
has named policies, the control plane now drops a table's policies with
|
|
853
|
+
the table, so their `db reset` takes every policy along — without listing
|
|
854
|
+
first the ones your files do not make again, and without reading the
|
|
855
|
+
warning that names them. A table left without a policy refuses every
|
|
856
|
+
Data API request until you create one again.
|
|
857
|
+
- **Check your clients before the backend updates.** The Data API now
|
|
858
|
+
refuses a string that SQLite would keep as text in a column declared with
|
|
859
|
+
INT, REAL, FLOA, DOUB, NUM, DEC, BOOL or BIT (a form's `"true"` for a
|
|
860
|
+
BOOLEAN, `"high"` for an INTEGER), even when no policy reads the column.
|
|
861
|
+
The backend worker takes this on at the project's next deploy or
|
|
862
|
+
`naro backend enable`, not when naro itself updates. Before that, look for
|
|
863
|
+
such rows: `naro db query "select count(*) from t where typeof(c) = 'text'"`
|
|
864
|
+
for each number or boolean column — a count above 0 means a client writes
|
|
865
|
+
strings there and needs fixing first.
|
|
866
|
+
|
|
612
867
|
## Machine-readable output (CI / AI agents)
|
|
613
868
|
|
|
614
869
|
Every command supports `--json`: stdout carries exactly one JSON object,
|
|
@@ -647,7 +902,9 @@ command run non-interactively without `--yes` fails with
|
|
|
647
902
|
|
|
648
903
|
`--yes` (or `-y`) suppresses every prompt — destructive actions
|
|
649
904
|
(production rollback/promote, env/domain removal, database reset, backend key
|
|
650
|
-
rotation, policy removal and drop
|
|
905
|
+
rotation, policy removal and drop, opening the preview with
|
|
906
|
+
`naro protection disable`, replacing or removing the bypass secret) run
|
|
907
|
+
unconfirmed, so use it deliberately in
|
|
651
908
|
automation.
|
|
652
909
|
|
|
653
910
|
## CI authentication
|
|
@@ -674,4 +931,5 @@ NARO_TOKEN=$(naro token) npx naro-cli deploy --prod --yes --json
|
|
|
674
931
|
| ------------------------ | ---------------------------------------------- |
|
|
675
932
|
| `NARO_TOKEN` | CI/agent access token (skips `naro login`) |
|
|
676
933
|
| `NARO_CONTROL_PLANE_URL` | Custom control plane (default https://naro.sh) |
|
|
934
|
+
| `NARO_SERVICE_KEY` | Service key for `naro storage ls\|cp\|rm` |
|
|
677
935
|
| `NARO_SUPABASE_URL/KEY` | Auth backend override (self-hosted setups) |
|