run402 1.38.0 → 1.39.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.
Files changed (2) hide show
  1. package/lib/projects.mjs +52 -1
  2. package/package.json +1 -1
package/lib/projects.mjs CHANGED
@@ -17,7 +17,10 @@ Subcommands:
17
17
  rest [id] <table> [params] Query a table via the REST API (PostgREST)
18
18
  usage [id] Show compute/storage usage for a project
19
19
  schema [id] Inspect the database schema
20
- rls [id] <template> <tables_json> Apply Row-Level Security policies
20
+ rls [id] <template> <tables_json> DEPRECATED (sunset 2026-05-23) - use 'apply-expose' instead
21
+ apply-expose [id] <manifest_json> Apply a declarative authorization manifest (supersedes 'rls')
22
+ apply-expose [id] --file <path> Apply a manifest from a JSON file
23
+ get-expose [id] Get the current authorization manifest
21
24
  delete [id] Immediately and irreversibly delete a project (cascade purge) and remove from local state
22
25
  pin [id] Pin a project (prevents expiry/GC)
23
26
  promote-user [id] <email> Promote a user to project_admin role
@@ -37,6 +40,8 @@ Examples:
37
40
  run402 projects usage abc123
38
41
  run402 projects schema abc123
39
42
  run402 projects rls abc123 public_read_authenticated_write '[{"table":"posts"}]'
43
+ run402 projects apply-expose abc123 --file manifest.json
44
+ run402 projects get-expose abc123
40
45
  run402 projects keys abc123
41
46
  run402 projects delete abc123
42
47
 
@@ -52,6 +57,14 @@ Notes:
52
57
  public_read_authenticated_write anyone reads; any authenticated user writes any row
53
58
  public_read_write_UNRESTRICTED fully open (anon_key writes); use 'run402 deploy' with a manifest
54
59
  that includes "i_understand_this_is_unrestricted": true
60
+ - 'rls' is deprecated (sunset 2026-05-23) — migrate to 'apply-expose'.
61
+ The expose manifest declares the full authorization surface (tables, views,
62
+ RPCs) in one convergent call. Tables not listed with expose:true are dark
63
+ by default. Sample manifest:
64
+ {"version":"1",
65
+ "tables":[{"name":"posts","expose":true,"policy":"user_owns_rows","owner_column":"user_id","force_owner_on_insert":true}],
66
+ "views":[],
67
+ "rpcs":[]}
55
68
  `;
56
69
 
57
70
  const SUB_HELP = {
@@ -169,6 +182,42 @@ async function rls(projectId, template, tablesJson) {
169
182
  console.log(JSON.stringify(data, null, 2));
170
183
  }
171
184
 
185
+ async function applyExpose(projectId, args = []) {
186
+ const p = findProject(projectId);
187
+ let file = null;
188
+ let inline = null;
189
+ for (let i = 0; i < args.length; i++) {
190
+ if (args[i] === "--file" && args[i + 1]) { file = args[++i]; }
191
+ else if (!inline && !args[i].startsWith("--")) { inline = args[i]; }
192
+ }
193
+ const raw = file ? readFileSync(file, "utf-8") : inline;
194
+ if (!raw) {
195
+ console.error(JSON.stringify({ status: "error", message: "Missing manifest. Provide inline JSON or use --file <path>" }));
196
+ process.exit(1);
197
+ }
198
+ let manifest;
199
+ try { manifest = JSON.parse(raw); }
200
+ catch { console.error(JSON.stringify({ status: "error", message: "Invalid JSON for manifest" })); process.exit(1); }
201
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/expose`, {
202
+ method: "POST",
203
+ headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
204
+ body: JSON.stringify(manifest),
205
+ });
206
+ const data = await res.json();
207
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
208
+ console.log(JSON.stringify(data, null, 2));
209
+ }
210
+
211
+ async function getExpose(projectId) {
212
+ const p = findProject(projectId);
213
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/expose`, {
214
+ headers: { "Authorization": `Bearer ${p.service_key}` },
215
+ });
216
+ const data = await res.json();
217
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
218
+ console.log(JSON.stringify(data, null, 2));
219
+ }
220
+
172
221
  async function list() {
173
222
  const store = loadKeyStore();
174
223
  const entries = Object.entries(store.projects);
@@ -335,6 +384,8 @@ export async function run(sub, args) {
335
384
  case "usage": { const { projectId } = resolvePositionalProject(args); await usage(projectId); break; }
336
385
  case "schema": { const { projectId } = resolvePositionalProject(args); await schema(projectId); break; }
337
386
  case "rls": { const { projectId, rest } = resolvePositionalProject(args); await rls(projectId, rest[0], rest[1]); break; }
387
+ case "apply-expose": { const { projectId, rest } = resolvePositionalProject(args); await applyExpose(projectId, rest); break; }
388
+ case "get-expose": { const { projectId } = resolvePositionalProject(args); await getExpose(projectId); break; }
338
389
  case "delete": { const { projectId } = resolvePositionalProject(args); await deleteProject(projectId); break; }
339
390
  case "pin": { const { projectId } = resolvePositionalProject(args); await pin(projectId); break; }
340
391
  case "promote-user": { const { projectId, rest } = resolvePositionalProject(args); await promoteUser(projectId, rest[0]); break; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "1.38.0",
3
+ "version": "1.39.0",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {