1tube 0.1.18 → 0.1.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "1tube",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "private": false,
5
5
  "description": "Self-hosted Supabase Edge Functions gateway for Deno, plus an ESLint plugin for consumers",
6
6
  "license": "MIT",
@@ -1352,89 +1352,85 @@ export function createWorkerdBackend(opts: WorkerdBackendOptions): WorkerdBacken
1352
1352
 
1353
1353
  // Preflight: refuse to spawn workerd against ports that are
1354
1354
  // already in use. Without this, a leftover workerd from a
1355
- // previous run silently steals our traffic and the failure
1356
- // surfaces only as wrong-looking response bodies. See
1357
- // `probeSocketsFree` doc comment for the full rationale.
1358
- // We skip the check on reload because the still-serving old
1359
- // generation occupies the OTHER slot's ports by design — the
1360
- // even/odd `slot` shift above guarantees no overlap, and
1361
- // probing here would race against that intentional overlap.
1362
- if (args.gen === 0) {
1363
- let conflicts = probeSocketsFree(capnp.routes);
1364
- if (conflicts.length > 0 && opts.killStaleWorkerd) {
1365
- console.log(
1366
- `[1tube] port preflight found ${conflicts.length} conflict(s); ` +
1367
- `--kill-stale-workerd is set, attempting cleanup...`,
1368
- );
1355
+ // previous run can satisfy the readiness probe and silently
1356
+ // steal our traffic; the failure then surfaces only as stale
1357
+ // response bodies. On reload the still-serving current process
1358
+ // occupies the OTHER slot's ports by design, so probing the next
1359
+ // slot is safe and catches orphaned older generations.
1360
+ let conflicts = probeSocketsFree(capnp.routes);
1361
+ if (conflicts.length > 0 && opts.killStaleWorkerd) {
1362
+ console.log(
1363
+ `[1tube] port preflight found ${conflicts.length} conflict(s)` +
1364
+ (args.gen > 0 ? ` for reload gen=${args.gen}` : "") +
1365
+ `; --kill-stale-workerd is set, attempting cleanup...`,
1366
+ );
1369
1367
 
1370
- // First try the precise fix: resolve the conflicted port(s)
1371
- // to their owning PID(s), and kill only owners named workerd.
1372
- // That is the behavior we want during firmware flashing:
1373
- // remove the stale candidate that owns 8800, but never kill
1374
- // a random non-workerd process that happens to be there.
1375
- const targeted = await killWorkerdOwnersOfPorts(conflicts);
1376
- if (targeted.attempted) {
1377
- if (targeted.code === 0) {
1378
- const details = targeted.stdout?.trim();
1379
- console.log(
1380
- `[1tube] targeted stale-workerd cleanup by port complete` +
1381
- (details ? ` (${details})` : "") +
1382
- `; re-probing...`,
1383
- );
1384
- } else {
1385
- console.warn(
1386
- `[1tube] targeted stale-workerd cleanup exited ${targeted.code}` +
1387
- (targeted.stderr ? `: ${targeted.stderr.trim()}` : ""),
1388
- );
1389
- }
1390
- await new Promise((r) => setTimeout(r, 250));
1391
- conflicts = probeSocketsFree(capnp.routes);
1368
+ // First try the precise fix: resolve the conflicted port(s)
1369
+ // to their owning PID(s), and kill only owners named workerd.
1370
+ // That is the behavior we want during firmware flashing:
1371
+ // remove the stale candidate that owns 8800, but never kill
1372
+ // a random non-workerd process that happens to be there.
1373
+ const targeted = await killWorkerdOwnersOfPorts(conflicts);
1374
+ if (targeted.attempted) {
1375
+ if (targeted.code === 0) {
1376
+ const details = targeted.stdout?.trim();
1377
+ console.log(
1378
+ `[1tube] targeted stale-workerd cleanup by port complete` +
1379
+ (details ? ` (${details})` : "") +
1380
+ `; re-probing...`,
1381
+ );
1382
+ } else {
1383
+ console.warn(
1384
+ `[1tube] targeted stale-workerd cleanup exited ${targeted.code}` +
1385
+ (targeted.stderr ? `: ${targeted.stderr.trim()}` : ""),
1386
+ );
1392
1387
  }
1388
+ await new Promise((r) => setTimeout(r, 250));
1389
+ conflicts = probeSocketsFree(capnp.routes);
1390
+ }
1393
1391
 
1394
- // If targeted cleanup did not exist on this platform, or it
1395
- // found no matching workerd owner, fall back to the old typed
1396
- // process-name cleanup.
1397
- const result = conflicts.length > 0 ? await killStaleWorkerd() : null;
1398
- if (result === null) {
1399
- // Already clear.
1400
- } else
1401
- if (result.attempted) {
1402
- // taskkill returns 128 when no matching image is running —
1403
- // that's not an error in our context (means the offender
1404
- // wasn't workerd, which is fine; preflight will catch it).
1405
- // pkill returns 1 in the same case.
1406
- const noMatch = (Deno.build.os === "windows" && result.code === 128) ||
1407
- (Deno.build.os !== "windows" && result.code === 1);
1408
- if (result.code === 0) {
1409
- console.log(`[1tube] killed leftover workerd process(es); re-probing...`);
1410
- } else if (noMatch) {
1411
- console.log(
1412
- `[1tube] no leftover workerd processes found; the conflict is held ` +
1413
- `by something else (re-probing to confirm)...`,
1414
- );
1415
- } else {
1416
- console.warn(
1417
- `[1tube] ${result.command?.join(" ")} exited ${result.code}` +
1418
- (result.stderr ? `: ${result.stderr.trim()}` : ""),
1419
- );
1420
- }
1421
- // Brief settle time: Windows TCP sockets in CLOSE_WAIT can
1422
- // hold the port for a fraction of a second after the owner
1423
- // process is killed. 250ms is empirically enough on the
1424
- // dev boxes we've tested without making the boot path feel
1425
- // sluggish.
1426
- await new Promise((r) => setTimeout(r, 250));
1427
- conflicts = probeSocketsFree(capnp.routes);
1392
+ // If targeted cleanup did not exist on this platform, or it
1393
+ // found no matching workerd owner, fall back to the old typed
1394
+ // process-name cleanup.
1395
+ const result = conflicts.length > 0 ? await killStaleWorkerd() : null;
1396
+ if (result === null) {
1397
+ // Already clear.
1398
+ } else if (result.attempted) {
1399
+ // taskkill returns 128 when no matching image is running —
1400
+ // that's not an error in our context (means the offender
1401
+ // wasn't workerd, which is fine; preflight will catch it).
1402
+ // pkill returns 1 in the same case.
1403
+ const noMatch = (Deno.build.os === "windows" && result.code === 128) ||
1404
+ (Deno.build.os !== "windows" && result.code === 1);
1405
+ if (result.code === 0) {
1406
+ console.log(`[1tube] killed leftover workerd process(es); re-probing...`);
1407
+ } else if (noMatch) {
1408
+ console.log(
1409
+ `[1tube] no leftover workerd processes found; the conflict is held ` +
1410
+ `by something else (re-probing to confirm)...`,
1411
+ );
1428
1412
  } else {
1429
1413
  console.warn(
1430
- `[1tube] could not run ${result.command?.join(" ")}: ` +
1431
- `${result.stderr ?? "unknown error"}`,
1414
+ `[1tube] ${result.command?.join(" ")} exited ${result.code}` +
1415
+ (result.stderr ? `: ${result.stderr.trim()}` : ""),
1432
1416
  );
1433
1417
  }
1418
+ // Brief settle time: Windows TCP sockets in CLOSE_WAIT can
1419
+ // hold the port for a fraction of a second after the owner
1420
+ // process is killed. 250ms is empirically enough on the
1421
+ // dev boxes we've tested without making the boot path feel
1422
+ // sluggish.
1423
+ await new Promise((r) => setTimeout(r, 250));
1424
+ conflicts = probeSocketsFree(capnp.routes);
1425
+ } else {
1426
+ console.warn(
1427
+ `[1tube] could not run ${result.command?.join(" ")}: ` +
1428
+ `${result.stderr ?? "unknown error"}`,
1429
+ );
1434
1430
  }
1435
- if (conflicts.length > 0) {
1436
- throw new Error(formatPortConflictError(conflicts));
1437
- }
1431
+ }
1432
+ if (conflicts.length > 0) {
1433
+ throw new Error(formatPortConflictError(conflicts));
1438
1434
  }
1439
1435
 
1440
1436
  const inspectorArgs = buildInspectorExtraArgs(opts.inspectorAddr, slot);
package/src/server.ts CHANGED
@@ -398,6 +398,15 @@ const LOCAL_SUPABASE_DEFAULTS: Record<string, string> = {
398
398
  JWT_SECRET: "super-secret-jwt-token-with-at-least-32-characters-long",
399
399
  };
400
400
 
401
+ const LOCAL_SUPABASE_STATUS_KEYS: Record<string, string[]> = {
402
+ SUPABASE_URL: ["API_URL"],
403
+ JWT_SECRET: ["JWT_SECRET"],
404
+ SUPABASE_ANON_KEY: ["ANON_KEY"],
405
+ SUPABASE_PUBLISHABLE_KEY: ["PUBLISHABLE_KEY"],
406
+ SUPABASE_SECRET_KEY: ["SECRET_KEY"],
407
+ SUPABASE_SERVICE_ROLE_KEY: ["SERVICE_ROLE_KEY"],
408
+ };
409
+
401
410
  // Only secrets the gateway *itself* uses on the hot path. Anything an edge
402
411
  // function happens to need (publishable/secret keys, OPENAI_API_KEY, …) is
403
412
  // the function author's contract with the host — refusing to boot because
@@ -405,8 +414,24 @@ const LOCAL_SUPABASE_DEFAULTS: Record<string, string> = {
405
414
  // confusing failures.
406
415
  const SECRETS_REQUIRED_IN_PROD = ["JWT_SECRET"];
407
416
 
408
- function applyDevDefaults() {
417
+ async function applyDevDefaults() {
409
418
  let applied = 0;
419
+ const statusEnv = await readLocalSupabaseStatusEnv();
420
+ for (const [target, sources] of Object.entries(LOCAL_SUPABASE_STATUS_KEYS)) {
421
+ if (Deno.env.get(target)) continue;
422
+ const value = sources
423
+ .map((source) => statusEnv[source])
424
+ .find((candidate) => typeof candidate === "string" && candidate.length > 0);
425
+ if (!value) continue;
426
+ Deno.env.set(target, value);
427
+ applied++;
428
+ }
429
+
430
+ if (!Deno.env.get("SUPABASE_URL") && Deno.env.get("VITE_SUPABASE_URL")) {
431
+ Deno.env.set("SUPABASE_URL", Deno.env.get("VITE_SUPABASE_URL")!);
432
+ applied++;
433
+ }
434
+
410
435
  for (const [key, value] of Object.entries(LOCAL_SUPABASE_DEFAULTS)) {
411
436
  if (!Deno.env.get(key)) {
412
437
  Deno.env.set(key, value);
@@ -418,6 +443,48 @@ function applyDevDefaults() {
418
443
  }
419
444
  }
420
445
 
446
+ async function readLocalSupabaseStatusEnv(): Promise<Record<string, string>> {
447
+ const runners: Array<{ command: string; args: string[] }> = [
448
+ { command: "supabase", args: ["status", "-o", "env"] },
449
+ { command: "bunx", args: ["supabase", "status", "-o", "env"] },
450
+ ];
451
+
452
+ for (const runner of runners) {
453
+ try {
454
+ const output = await new Deno.Command(runner.command, {
455
+ args: runner.args,
456
+ stdout: "piped",
457
+ stderr: "null",
458
+ }).output();
459
+ if (!output.success) continue;
460
+ return parseSupabaseStatusEnv(new TextDecoder().decode(output.stdout));
461
+ } catch {
462
+ // CLI not installed on PATH (or bunx unavailable). Try the next runner.
463
+ }
464
+ }
465
+ return {};
466
+ }
467
+
468
+ function parseSupabaseStatusEnv(text: string): Record<string, string> {
469
+ const out: Record<string, string> = {};
470
+ for (const rawLine of text.split(/\r?\n/)) {
471
+ const line = rawLine.trim();
472
+ if (!line || line.startsWith("#")) continue;
473
+ const eq = line.indexOf("=");
474
+ if (eq <= 0) continue;
475
+ const key = line.slice(0, eq).trim();
476
+ let value = line.slice(eq + 1).trim();
477
+ if (
478
+ (value.startsWith('"') && value.endsWith('"')) ||
479
+ (value.startsWith("'") && value.endsWith("'"))
480
+ ) {
481
+ value = value.slice(1, -1);
482
+ }
483
+ out[key] = value;
484
+ }
485
+ return out;
486
+ }
487
+
421
488
  function enforceProdSecrets() {
422
489
  // We deliberately do NOT reject "well-known dev default" values
423
490
  // here. The local Supabase Docker stack ships with a fixed
@@ -463,7 +530,7 @@ function requireInternal(c: Context): Response | null {
463
530
  }
464
531
 
465
532
  if (opts.dev) {
466
- applyDevDefaults();
533
+ await applyDevDefaults();
467
534
  } else {
468
535
  enforceProdSecrets();
469
536
  }