run402 1.64.0 → 1.65.1

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 CHANGED
@@ -47,6 +47,7 @@ run402 allowance export # print address (for funding)
47
47
 
48
48
  ```bash
49
49
  run402 projects sql <id> "CREATE TABLE items (id serial PRIMARY KEY, …)"
50
+ run402 projects validate-expose <id> --file manifest.json # check auth manifest, no mutation
50
51
  run402 projects apply-expose <id> --file manifest.json # declare what's reachable
51
52
  run402 projects rest <id> items "select=*&order=id.desc&limit=10"
52
53
  run402 projects schema <id> # introspect tables + RLS
@@ -113,7 +114,7 @@ run402 functions deploy <id> my-fn --file fn.ts \
113
114
  --timeout 30 --memory 256 \
114
115
  --schedule "*/15 * * * *" \
115
116
  --deps "stripe,zod@^3"
116
- run402 functions logs <id> my-fn --tail 100 --follow
117
+ run402 functions logs <id> my-fn --tail 100 --request-id req_abc123 --follow
117
118
  run402 functions invoke <id> my-fn --body '{"hello":"world"}'
118
119
  ```
119
120
 
package/lib/deploy-v2.mjs CHANGED
@@ -526,6 +526,14 @@ const ROUTE_WARNING_GUIDANCE = {
526
526
  "Add method coverage or static files deliberately.",
527
527
  ],
528
528
  },
529
+ WILDCARD_ROUTE_EXCLUDES_MUTATION_METHODS: {
530
+ hint: "A wildcard function route only allows GET/HEAD, so POST/PUT/PATCH/DELETE paths under that prefix will be rejected before the function runs.",
531
+ next_actions: [
532
+ "Add the mutation methods the routed function supports, such as POST.",
533
+ "Omit methods to allow every supported method when the route is an API surface.",
534
+ "Use --allow-warnings only if the wildcard prefix is intentionally read-only.",
535
+ ],
536
+ },
529
537
  ROUTE_TABLE_NEAR_LIMIT: {
530
538
  hint: "The route table is near the gateway/project limit.",
531
539
  next_actions: [
package/lib/functions.mjs CHANGED
@@ -4,6 +4,8 @@ import { getSdk } from "./sdk.mjs";
4
4
  import { reportSdkError, fail } from "./sdk-errors.mjs";
5
5
  import { assertKnownFlags, hasHelp, normalizeArgv, parseIntegerFlag, validateRegularFile } from "./argparse.mjs";
6
6
 
7
+ const FUNCTION_LOG_REQUEST_ID_RE = /^req_[A-Za-z0-9_-]{4,128}$/;
8
+
7
9
  const HELP = `run402 functions — Manage serverless functions
8
10
 
9
11
  Usage:
@@ -14,7 +16,7 @@ Subcommands:
14
16
  Deploy a function to a project
15
17
  invoke <id> <name> [--method <M>] [--body <json>]
16
18
  Invoke a deployed function
17
- logs <id> <name> [--tail <n>] [--since <ts>] [--follow]
19
+ logs <id> <name> [--tail <n>] [--since <ts>] [--request-id <req_...>] [--follow]
18
20
  Get function logs
19
21
  update <id> <name> [--schedule <cron>] [--schedule-remove] [--timeout <s>] [--memory <mb>]
20
22
  Update function schedule or config without re-deploying
@@ -28,6 +30,7 @@ Examples:
28
30
  run402 functions invoke prj_abc123 stripe-webhook --body '{"event":"test"}'
29
31
  run402 functions logs prj_abc123 stripe-webhook --tail 100
30
32
  run402 functions logs prj_abc123 stripe-webhook --since 2026-03-29T14:00:00Z
33
+ run402 functions logs prj_abc123 stripe-webhook --request-id req_abc123
31
34
  run402 functions logs prj_abc123 stripe-webhook --follow
32
35
  run402 functions update prj_abc123 send-reminders --schedule '0 */4 * * *'
33
36
  run402 functions update prj_abc123 send-reminders --schedule-remove
@@ -110,13 +113,15 @@ Arguments:
110
113
  <name> Function name
111
114
 
112
115
  Options:
113
- --tail <n> Number of most-recent entries (default 50)
116
+ --tail <n> Number of most-recent entries (default 50, max 1000)
114
117
  --since <ts> ISO timestamp or epoch ms; only entries after this
118
+ --request-id <id> Only entries correlated to this req_... request id
115
119
  --follow Poll every 3s and stream new entries (Ctrl-C to stop)
116
120
 
117
121
  Examples:
118
122
  run402 functions logs prj_abc123 stripe-webhook --tail 100
119
123
  run402 functions logs prj_abc123 stripe-webhook --since 2026-03-29T14:00:00Z
124
+ run402 functions logs prj_abc123 stripe-webhook --request-id req_abc123
120
125
  run402 functions logs prj_abc123 stripe-webhook --follow
121
126
  `,
122
127
  update: `run402 functions update — Update function config without re-deploying
@@ -227,14 +232,16 @@ async function invoke(projectId, name, args) {
227
232
  }
228
233
 
229
234
  async function logs(projectId, name, args) {
230
- assertRequiredProjectAndName(projectId, name, "run402 functions logs <project_id> <name> [--tail <n>]");
231
- assertKnownFlags(args, ["--tail", "--since", "--follow", "--help", "-h"], ["--tail", "--since"]);
235
+ assertRequiredProjectAndName(projectId, name, "run402 functions logs <project_id> <name> [--tail <n>] [--request-id <req_...>]");
236
+ assertKnownFlags(args, ["--tail", "--since", "--request-id", "--follow", "--help", "-h"], ["--tail", "--since", "--request-id"]);
232
237
  let tail = 50;
233
238
  let since = undefined;
239
+ let requestId = undefined;
234
240
  let follow = false;
235
241
  for (let i = 0; i < args.length; i++) {
236
242
  if (args[i] === "--tail") tail = parseIntegerFlag("--tail", args[++i], { min: 1 });
237
243
  if (args[i] === "--since" && args[i + 1]) since = args[++i];
244
+ if (args[i] === "--request-id" && args[i + 1]) requestId = args[++i];
238
245
  if (args[i] === "--follow") follow = true;
239
246
  }
240
247
 
@@ -254,12 +261,20 @@ async function logs(projectId, name, args) {
254
261
  }
255
262
  sinceIso = new Date(ms).toISOString();
256
263
  }
264
+ if (requestId !== undefined && !FUNCTION_LOG_REQUEST_ID_RE.test(requestId)) {
265
+ fail({
266
+ code: "BAD_USAGE",
267
+ message: `Invalid --request-id value: ${requestId}`,
268
+ details: { flag: "--request-id", value: requestId, expected: "req_<4-128 url-safe chars>" },
269
+ });
270
+ }
257
271
 
258
272
  const fetchLogs = async () => {
259
273
  try {
260
274
  const data = await getSdk().functions.logs(projectId, name, {
261
275
  tail,
262
276
  since: sinceIso,
277
+ requestId,
263
278
  });
264
279
  return data.logs || [];
265
280
  } catch (err) {
@@ -278,27 +293,60 @@ async function logs(projectId, name, args) {
278
293
  let running = true;
279
294
  process.on("SIGINT", () => { running = false; });
280
295
 
281
- const initial = await fetchLogs();
282
- for (const entry of initial) {
283
- console.log(`[${entry.timestamp}] ${entry.message}`);
284
- }
285
- if (initial.length > 0) {
286
- sinceIso = new Date(new Date(initial[initial.length - 1].timestamp).getTime() + 1).toISOString();
287
- }
296
+ let highWaterMs = sinceIso === undefined ? Number.NEGATIVE_INFINITY : new Date(sinceIso).getTime();
297
+ let seenAtHighWater = new Set();
288
298
 
289
- while (running) {
290
- await new Promise(r => setTimeout(r, 3000));
291
- if (!running) break;
292
- const entries = await fetchLogs();
299
+ const printFreshEntries = (entries) => {
300
+ let nextHighWaterMs = highWaterMs;
301
+ const fresh = [];
293
302
  for (const entry of entries) {
303
+ const entryMs = logTimestampMs(entry);
304
+ const identity = logEntryIdentity(entry);
305
+ if (entryMs < highWaterMs) continue;
306
+ if (entryMs === highWaterMs && seenAtHighWater.has(identity)) continue;
307
+ fresh.push({ entry, entryMs, identity });
308
+ if (entryMs > nextHighWaterMs) nextHighWaterMs = entryMs;
309
+ }
310
+
311
+ for (const { entry } of fresh) {
294
312
  console.log(`[${entry.timestamp}] ${entry.message}`);
295
313
  }
296
- if (entries.length > 0) {
297
- sinceIso = new Date(new Date(entries[entries.length - 1].timestamp).getTime() + 1).toISOString();
314
+ if (fresh.length === 0 || !Number.isFinite(nextHighWaterMs)) return;
315
+
316
+ const nextSeenAtHighWater = new Set();
317
+ for (const entry of entries) {
318
+ if (logTimestampMs(entry) === nextHighWaterMs) {
319
+ nextSeenAtHighWater.add(logEntryIdentity(entry));
320
+ }
298
321
  }
322
+ for (const { entry, entryMs, identity } of fresh) {
323
+ if (entryMs === nextHighWaterMs) {
324
+ nextSeenAtHighWater.add(identity);
325
+ }
326
+ }
327
+ highWaterMs = nextHighWaterMs;
328
+ seenAtHighWater = nextSeenAtHighWater;
329
+ sinceIso = new Date(highWaterMs).toISOString();
330
+ };
331
+
332
+ printFreshEntries(await fetchLogs());
333
+
334
+ while (running) {
335
+ await new Promise(r => setTimeout(r, 3000));
336
+ if (!running) break;
337
+ printFreshEntries(await fetchLogs());
299
338
  }
300
339
  }
301
340
 
341
+ function logTimestampMs(entry) {
342
+ const ms = new Date(entry.timestamp).getTime();
343
+ return Number.isNaN(ms) ? 0 : ms;
344
+ }
345
+
346
+ function logEntryIdentity(entry) {
347
+ return entry.event_id || `${entry.log_stream_name || ""}:${entry.timestamp || ""}:${entry.message || ""}`;
348
+ }
349
+
302
350
  async function update(projectId, name, args) {
303
351
  assertRequiredProjectAndName(projectId, name, "run402 functions update <project_id> <name> [options]");
304
352
  assertKnownFlags(args, ["--schedule", "--schedule-remove", "--timeout", "--memory", "--help", "-h"], ["--schedule", "--timeout", "--memory"]);
package/lib/projects.mjs CHANGED
@@ -23,6 +23,8 @@ Subcommands:
23
23
  schema [id] Inspect the database schema
24
24
  apply-expose [id] <manifest_json> Apply a declarative authorization manifest
25
25
  apply-expose [id] --file <path> Apply a manifest from a JSON file
26
+ validate-expose [id] <manifest_json> Validate an authorization manifest without applying it
27
+ validate-expose [id] --file <path> Validate a manifest file without mutating the project
26
28
  get-expose [id] Get the current authorization manifest
27
29
  delete [id] --confirm Immediately and irreversibly delete a project (cascade purge) and remove from local state. Requires --confirm.
28
30
  pin [id] Pin a project (admin only; uses admin allowance wallet)
@@ -43,6 +45,7 @@ Examples:
43
45
  run402 projects usage prj_abc123
44
46
  run402 projects costs prj_abc123 --window 30d
45
47
  run402 projects schema prj_abc123
48
+ run402 projects validate-expose prj_abc123 --file manifest.json
46
49
  run402 projects apply-expose prj_abc123 --file manifest.json
47
50
  run402 projects get-expose prj_abc123
48
51
  run402 projects keys prj_abc123
@@ -68,6 +71,9 @@ Notes:
68
71
  row), public_read_write_UNRESTRICTED (fully open; requires
69
72
  "i_understand_this_is_unrestricted": true on the entry), custom (provide
70
73
  custom_sql with CREATE POLICY statements).
74
+ - 'validate-expose' checks the same auth/expose manifest shape without
75
+ applying it. Optional migration SQL is used only for reference checks; it is
76
+ not executed as a PostgreSQL dry run.
71
77
  `;
72
78
 
73
79
  const SUB_HELP = {
@@ -135,6 +141,35 @@ Examples:
135
141
  run402 projects costs prj_abc123
136
142
  RUN402_ADMIN_COOKIE='run402_admin=...' run402 projects costs prj_abc123
137
143
  RUN402_ADMIN_COOKIE='run402_admin=...' run402 projects costs --window 7d
144
+ `,
145
+ "validate-expose": `run402 projects validate-expose — Validate an authorization manifest without applying it
146
+
147
+ Usage:
148
+ run402 projects validate-expose [id] <manifest_json> [options]
149
+ run402 projects validate-expose [id] --file <path> [options]
150
+ cat manifest.json | run402 projects validate-expose [id] [options]
151
+
152
+ Arguments:
153
+ [id] Optional project ID. When omitted, the active project is
154
+ used if one is set; otherwise validation is projectless.
155
+ <manifest_json> Inline auth/expose manifest JSON.
156
+
157
+ Options:
158
+ --file <path> Read the auth/expose manifest from a JSON file
159
+ --migration-file <path> Read migration SQL for reference checks only
160
+ --migration-sql <sql> Inline migration SQL for reference checks only
161
+
162
+ Notes:
163
+ - This validates the auth/expose manifest used by manifest.json,
164
+ database.expose, and apply-expose. It does not validate deploy manifests.
165
+ - Migration SQL is parsed as context for references; it is not executed.
166
+ - Validation findings are returned in JSON with hasErrors and do not make the
167
+ command fail. Usage, file, auth, and network errors still exit non-zero.
168
+
169
+ Examples:
170
+ run402 projects validate-expose --file manifest.json
171
+ run402 projects validate-expose prj_abc123 --file manifest.json --migration-file setup.sql
172
+ run402 projects validate-expose '{"version":"1","tables":[]}'
138
173
  `,
139
174
  };
140
175
 
@@ -237,6 +272,86 @@ async function applyExpose(projectId, args = []) {
237
272
  }
238
273
  }
239
274
 
275
+ async function validateExpose(args = []) {
276
+ let projectId = null;
277
+ let file = null;
278
+ let inline = null;
279
+ let migrationFile = null;
280
+ let migrationSql = null;
281
+ for (let i = 0; i < args.length; i++) {
282
+ const arg = args[i];
283
+ if (arg === "--file") {
284
+ if (args[i + 1] === undefined) {
285
+ fail({ code: "BAD_FLAG", message: "--file requires a value", details: { flag: "--file" } });
286
+ }
287
+ file = args[++i];
288
+ }
289
+ else if (arg === "--migration-file") {
290
+ if (args[i + 1] === undefined) {
291
+ fail({ code: "BAD_FLAG", message: "--migration-file requires a value", details: { flag: "--migration-file" } });
292
+ }
293
+ migrationFile = args[++i];
294
+ }
295
+ else if (arg === "--migration-sql") {
296
+ if (args[i + 1] === undefined) {
297
+ fail({ code: "BAD_FLAG", message: "--migration-sql requires a value", details: { flag: "--migration-sql" } });
298
+ }
299
+ migrationSql = args[++i];
300
+ }
301
+ else if (!projectId && typeof arg === "string" && arg.startsWith("prj_")) { projectId = arg; }
302
+ else if (!inline && typeof arg === "string" && !arg.startsWith("--")) { inline = arg; }
303
+ else if (typeof arg === "string" && !arg.startsWith("--")) {
304
+ fail({
305
+ code: "BAD_USAGE",
306
+ message: `Unexpected extra argument: ${arg}`,
307
+ hint: "run402 projects validate-expose [id] <manifest_json>",
308
+ });
309
+ }
310
+ }
311
+ if (file && inline) {
312
+ fail({
313
+ code: "BAD_USAGE",
314
+ message: "Provide either inline manifest JSON or --file <path>, not both.",
315
+ hint: "run402 projects validate-expose [id] --file manifest.json",
316
+ });
317
+ }
318
+ if (migrationFile && migrationSql !== null) {
319
+ fail({
320
+ code: "BAD_USAGE",
321
+ message: "Provide either --migration-file or --migration-sql, not both.",
322
+ });
323
+ }
324
+ if (file) validateRegularFile(file, "--file");
325
+ if (migrationFile) validateRegularFile(migrationFile, "--migration-file");
326
+
327
+ let raw = file ? readFileSync(file, "utf-8") : inline;
328
+ if (!raw && process.stdin && process.stdin.isTTY === false) {
329
+ raw = readFileSync(0, "utf-8");
330
+ }
331
+ if (!raw) {
332
+ fail({
333
+ code: "BAD_USAGE",
334
+ message: "Missing manifest.",
335
+ hint: "Provide inline JSON, pipe JSON to stdin, or use --file <path>",
336
+ });
337
+ }
338
+
339
+ const activeProjectId = getActiveProjectId();
340
+ const project = projectId || activeProjectId || undefined;
341
+ if (!project) allowanceAuthHeaders("/projects/v1/expose/validate");
342
+ const migration = migrationFile ? readFileSync(migrationFile, "utf-8") : migrationSql;
343
+
344
+ try {
345
+ const data = await getSdk().projects.validateExpose(raw, {
346
+ ...(project ? { project } : {}),
347
+ ...(migration !== null && migration !== undefined ? { migrationSql: migration } : {}),
348
+ });
349
+ console.log(JSON.stringify({ status: "ok", ...data }, null, 2));
350
+ } catch (err) {
351
+ reportSdkError(err);
352
+ }
353
+ }
354
+
240
355
  async function getExpose(projectId) {
241
356
  try {
242
357
  const data = await getSdk().projects.getExpose(projectId);
@@ -463,6 +578,10 @@ const FLAGS_BY_SUB = {
463
578
  sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
464
579
  costs: { known: ["--window"], values: ["--window"] },
465
580
  "apply-expose": { known: ["--file"], values: ["--file"] },
581
+ "validate-expose": {
582
+ known: ["--file", "--migration-file", "--migration-sql"],
583
+ values: ["--file", "--migration-file", "--migration-sql"],
584
+ },
466
585
  delete: { known: ["--confirm"], values: [] },
467
586
  };
468
587
 
@@ -495,6 +614,7 @@ export async function run(sub, args) {
495
614
  case "costs": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.costs.values }); await costs(projectId, rest); break; }
496
615
  case "schema": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await schema(projectId); break; }
497
616
  case "apply-expose": { const { projectId, rest } = resolvePositionalProject(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB["apply-expose"].values, rejectBareFirstWhenFlagPresent: ["--file"] }); await applyExpose(projectId, rest); break; }
617
+ case "validate-expose": await validateExpose(args); break;
498
618
  case "get-expose": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await getExpose(projectId); break; }
499
619
  case "delete": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true }); await deleteProject(projectId, rest); break; }
500
620
  case "pin": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await pin(projectId); break; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "1.64.0",
3
+ "version": "1.65.1",
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": {
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
@@ -406,11 +406,11 @@ async function planInternal(client, spec, idempotencyKey, dryRun = false) {
406
406
  }
407
407
  let plan;
408
408
  try {
409
- plan = normalizePlanResponse(await client.request(dryRun ? "/deploy/v2/plans?dry_run=true" : "/deploy/v2/plans", {
409
+ plan = withClientPlanWarnings(normalized, normalizePlanResponse(await client.request(dryRun ? "/deploy/v2/plans?dry_run=true" : "/deploy/v2/plans", {
410
410
  method: "POST",
411
411
  body,
412
412
  context: "planning deploy",
413
- }));
413
+ })));
414
414
  }
415
415
  catch (err) {
416
416
  throw translateDeployError(err, "plan", null, null);
@@ -1297,6 +1297,59 @@ function emitPlanWarnings(plan, emit) {
1297
1297
  emit({ type: "plan.warnings", warnings: plan.warnings });
1298
1298
  }
1299
1299
  }
1300
+ function withClientPlanWarnings(spec, plan) {
1301
+ const warnings = clientRoutePlanWarnings(spec);
1302
+ if (warnings.length === 0)
1303
+ return plan;
1304
+ const seen = new Set(plan.warnings.map((warning) => warningKey(warning)));
1305
+ const nextWarnings = [...plan.warnings];
1306
+ for (const warning of warnings) {
1307
+ const key = warningKey(warning);
1308
+ if (seen.has(key))
1309
+ continue;
1310
+ seen.add(key);
1311
+ nextWarnings.push(warning);
1312
+ }
1313
+ return { ...plan, warnings: nextWarnings };
1314
+ }
1315
+ function warningKey(warning) {
1316
+ return `${warning.code}:${(warning.affected ?? []).join(",")}`;
1317
+ }
1318
+ function clientRoutePlanWarnings(spec) {
1319
+ const routes = spec.routes;
1320
+ if (!routes || !("replace" in routes))
1321
+ return [];
1322
+ const affected = routes.replace
1323
+ .filter((route) => {
1324
+ if (route.target.type !== "function")
1325
+ return false;
1326
+ if (!route.pattern.endsWith("/*"))
1327
+ return false;
1328
+ if (!route.methods)
1329
+ return false;
1330
+ const methods = new Set(route.methods);
1331
+ return (methods.size > 0 &&
1332
+ [...methods].every((method) => method === "GET" || method === "HEAD"));
1333
+ })
1334
+ .map((route) => route.pattern)
1335
+ .sort();
1336
+ if (affected.length === 0)
1337
+ return [];
1338
+ return [
1339
+ {
1340
+ code: "WILDCARD_ROUTE_EXCLUDES_MUTATION_METHODS",
1341
+ severity: "warn",
1342
+ requires_confirmation: true,
1343
+ message: "A wildcard function route only allows GET/HEAD. Mutation endpoints under that prefix will be rejected by the gateway before the function runs.",
1344
+ affected,
1345
+ confidence: "heuristic",
1346
+ details: {
1347
+ missing_common_methods: ["POST", "PUT", "PATCH", "DELETE"],
1348
+ fix: "Add the mutation methods your routed function supports, or omit methods to allow every supported method.",
1349
+ },
1350
+ },
1351
+ ];
1352
+ }
1300
1353
  function abortOnConfirmationWarnings(plan, opts) {
1301
1354
  if (opts.allowWarnings)
1302
1355
  return;