run402 1.21.0 → 1.23.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/lib/functions.mjs CHANGED
@@ -12,7 +12,10 @@ Subcommands:
12
12
  Deploy a function to a project
13
13
  invoke <id> <name> [--method <M>] [--body <json>]
14
14
  Invoke a deployed function
15
- logs <id> <name> [--tail <n>] Get function logs
15
+ logs <id> <name> [--tail <n>] [--since <ts>] [--follow]
16
+ Get function logs
17
+ update <id> <name> [--schedule <cron>] [--schedule-remove] [--timeout <s>] [--memory <mb>]
18
+ Update function schedule or config without re-deploying
16
19
  list <id> List all functions for a project
17
20
  delete <id> <name> Delete a function
18
21
 
@@ -22,6 +25,11 @@ Examples:
22
25
  run402 functions deploy abc123 send-reminders --file remind.ts --schedule '' # remove schedule
23
26
  run402 functions invoke abc123 stripe-webhook --body '{"event":"test"}'
24
27
  run402 functions logs abc123 stripe-webhook --tail 100
28
+ run402 functions logs abc123 stripe-webhook --since 2026-03-29T14:00:00Z
29
+ run402 functions logs abc123 stripe-webhook --follow
30
+ run402 functions update abc123 send-reminders --schedule '0 */4 * * *'
31
+ run402 functions update abc123 send-reminders --schedule-remove
32
+ run402 functions update abc123 my-func --timeout 15 --memory 256
25
33
  run402 functions list abc123
26
34
  run402 functions delete abc123 stripe-webhook
27
35
 
@@ -84,11 +92,94 @@ async function invoke(projectId, name, args) {
84
92
  async function logs(projectId, name, args) {
85
93
  const p = findProject(projectId);
86
94
  let tail = 50;
95
+ let since = undefined;
96
+ let follow = false;
87
97
  for (let i = 0; i < args.length; i++) {
88
98
  if (args[i] === "--tail" && args[i + 1]) tail = parseInt(args[++i]);
99
+ if (args[i] === "--since" && args[i + 1]) since = args[++i];
100
+ if (args[i] === "--follow") follow = true;
89
101
  }
90
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}/logs?tail=${tail}`, {
91
- headers: { "Authorization": `Bearer ${p.service_key}` },
102
+
103
+ // Parse since: accept ISO string or epoch ms
104
+ let sinceMs = undefined;
105
+ if (since !== undefined) {
106
+ const parsed = Number(since);
107
+ sinceMs = Number.isNaN(parsed) ? new Date(since).getTime() : parsed;
108
+ if (Number.isNaN(sinceMs)) { console.error(JSON.stringify({ status: "error", message: `Invalid --since value: ${since}` })); process.exit(1); }
109
+ }
110
+
111
+ const fetchLogs = async () => {
112
+ let url = `${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}/logs?tail=${tail}`;
113
+ if (sinceMs !== undefined) url += `&since=${sinceMs}`;
114
+ const res = await fetch(url, { headers: { "Authorization": `Bearer ${p.service_key}` } });
115
+ const data = await res.json();
116
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
117
+ return data.logs || [];
118
+ };
119
+
120
+ if (!follow) {
121
+ const entries = await fetchLogs();
122
+ console.log(JSON.stringify({ logs: entries }, null, 2));
123
+ return;
124
+ }
125
+
126
+ // Follow mode: poll every 3s, print new entries
127
+ let running = true;
128
+ process.on("SIGINT", () => { running = false; });
129
+
130
+ // Initial fetch
131
+ const initial = await fetchLogs();
132
+ for (const entry of initial) {
133
+ console.log(`[${entry.timestamp}] ${entry.message}`);
134
+ }
135
+ if (initial.length > 0) {
136
+ sinceMs = new Date(initial[initial.length - 1].timestamp).getTime() + 1;
137
+ }
138
+
139
+ while (running) {
140
+ await new Promise(r => setTimeout(r, 3000));
141
+ if (!running) break;
142
+ const entries = await fetchLogs();
143
+ for (const entry of entries) {
144
+ console.log(`[${entry.timestamp}] ${entry.message}`);
145
+ }
146
+ if (entries.length > 0) {
147
+ sinceMs = new Date(entries[entries.length - 1].timestamp).getTime() + 1;
148
+ }
149
+ }
150
+ }
151
+
152
+ async function update(projectId, name, args) {
153
+ const p = findProject(projectId);
154
+ let schedule = undefined;
155
+ let scheduleRemove = false;
156
+ let timeout = undefined;
157
+ let memory = undefined;
158
+ for (let i = 0; i < args.length; i++) {
159
+ if (args[i] === "--schedule" && i + 1 < args.length) schedule = args[++i];
160
+ if (args[i] === "--schedule-remove") scheduleRemove = true;
161
+ if (args[i] === "--timeout" && args[i + 1]) timeout = parseInt(args[++i]);
162
+ if (args[i] === "--memory" && args[i + 1]) memory = parseInt(args[++i]);
163
+ }
164
+ const body = {};
165
+ if (scheduleRemove || schedule === "") {
166
+ body.schedule = null;
167
+ } else if (schedule !== undefined) {
168
+ body.schedule = schedule;
169
+ }
170
+ if (timeout !== undefined || memory !== undefined) {
171
+ body.config = {};
172
+ if (timeout !== undefined) body.config.timeout = timeout;
173
+ if (memory !== undefined) body.config.memory = memory;
174
+ }
175
+ if (Object.keys(body).length === 0) {
176
+ console.error(JSON.stringify({ status: "error", message: "Provide at least one of: --schedule, --schedule-remove, --timeout, --memory" }));
177
+ process.exit(1);
178
+ }
179
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}`, {
180
+ method: "PATCH",
181
+ headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
182
+ body: JSON.stringify(body),
92
183
  });
93
184
  const data = await res.json();
94
185
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
@@ -125,6 +216,7 @@ export async function run(sub, args) {
125
216
  case "deploy": await deploy(args[0], args[1], args.slice(2)); break;
126
217
  case "invoke": await invoke(args[0], args[1], args.slice(2)); break;
127
218
  case "logs": await logs(args[0], args[1], args.slice(2)); break;
219
+ case "update": await update(args[0], args[1], args.slice(2)); break;
128
220
  case "list": await list(args[0]); break;
129
221
  case "delete": await deleteFunction(args[0], args[1]); break;
130
222
  default:
package/lib/projects.mjs CHANGED
@@ -20,6 +20,8 @@ Subcommands:
20
20
  rls <id> <template> <tables_json> Apply Row-Level Security policies
21
21
  delete <id> Delete a project and remove it from local state
22
22
  pin <id> Pin a project (prevents expiry/GC)
23
+ promote-user <id> <email> Promote a user to project_admin role
24
+ demote-user <id> <email> Demote a user from project_admin role
23
25
 
24
26
  Examples:
25
27
  run402 projects quote
@@ -190,6 +192,32 @@ async function pin(projectId) {
190
192
  console.log(JSON.stringify(data, null, 2));
191
193
  }
192
194
 
195
+ async function promoteUser(projectId, email) {
196
+ if (!email) { console.error(JSON.stringify({ status: "error", message: "Usage: run402 projects promote-user <project_id> <email>" })); process.exit(1); }
197
+ const p = findProject(projectId);
198
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/promote-user`, {
199
+ method: "POST",
200
+ headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
201
+ body: JSON.stringify({ email }),
202
+ });
203
+ const data = await res.json();
204
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
205
+ console.log(JSON.stringify(data, null, 2));
206
+ }
207
+
208
+ async function demoteUser(projectId, email) {
209
+ if (!email) { console.error(JSON.stringify({ status: "error", message: "Usage: run402 projects demote-user <project_id> <email>" })); process.exit(1); }
210
+ const p = findProject(projectId);
211
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/demote-user`, {
212
+ method: "POST",
213
+ headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
214
+ body: JSON.stringify({ email }),
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
+
193
221
  async function deleteProject(projectId) {
194
222
  const p = findProject(projectId);
195
223
  const res = await fetch(`${API}/projects/v1/${projectId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${p.service_key}` } });
@@ -220,7 +248,9 @@ export async function run(sub, args) {
220
248
  case "schema": await schema(args[0]); break;
221
249
  case "rls": await rls(args[0], args[1], args[2]); break;
222
250
  case "delete": await deleteProject(args[0]); break;
223
- case "pin": await pin(args[0]); break;
251
+ case "pin": await pin(args[0]); break;
252
+ case "promote-user": await promoteUser(args[0], args[1]); break;
253
+ case "demote-user": await demoteUser(args[0], args[1]); break;
224
254
  default:
225
255
  console.error(`Unknown subcommand: ${sub}\n`);
226
256
  console.log(HELP);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "1.21.0",
3
+ "version": "1.23.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": {