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 +95 -3
- package/lib/projects.mjs +31 -1
- package/package.json +1 -1
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>]
|
|
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
|
-
|
|
91
|
-
|
|
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":
|
|
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