ugly-app 0.1.518 → 0.1.521

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 (120) hide show
  1. package/.pgdata/base/16384/pg_internal.init +0 -0
  2. package/.pgdata/global/pg_control +0 -0
  3. package/.pgdata/global/pg_internal.init +0 -0
  4. package/.pgdata/pg_wal/000000010000000000000004 +0 -0
  5. package/.pgdata/postmaster.pid +2 -2
  6. package/dist/cli/dev.d.ts.map +1 -1
  7. package/dist/cli/dev.js +0 -3
  8. package/dist/cli/dev.js.map +1 -1
  9. package/dist/cli/doctor.d.ts.map +1 -1
  10. package/dist/cli/doctor.js +31 -66
  11. package/dist/cli/doctor.js.map +1 -1
  12. package/dist/cli/index.js +8 -66
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/migrate.d.ts.map +1 -1
  15. package/dist/cli/migrate.js +0 -4
  16. package/dist/cli/migrate.js.map +1 -1
  17. package/dist/cli/schemaGen.d.ts.map +1 -1
  18. package/dist/cli/schemaGen.js +0 -4
  19. package/dist/cli/schemaGen.js.map +1 -1
  20. package/dist/cli/schemaStatus.d.ts.map +1 -1
  21. package/dist/cli/schemaStatus.js +0 -4
  22. package/dist/cli/schemaStatus.js.map +1 -1
  23. package/dist/cli/serverLogQueryApi.d.ts +5 -4
  24. package/dist/cli/serverLogQueryApi.d.ts.map +1 -1
  25. package/dist/cli/serverLogQueryApi.js +55 -115
  26. package/dist/cli/serverLogQueryApi.js.map +1 -1
  27. package/dist/cli/version.d.ts +1 -1
  28. package/dist/cli/version.js +1 -1
  29. package/dist/client/auth/MagicLinkCallback.d.ts.map +1 -1
  30. package/dist/client/auth/MagicLinkCallback.js +8 -2
  31. package/dist/client/auth/MagicLinkCallback.js.map +1 -1
  32. package/dist/client/auth/MagicLinkForm.d.ts.map +1 -1
  33. package/dist/client/auth/MagicLinkForm.js +6 -1
  34. package/dist/client/auth/MagicLinkForm.js.map +1 -1
  35. package/dist/server/App.d.ts.map +1 -1
  36. package/dist/server/App.js +1 -2
  37. package/dist/server/App.js.map +1 -1
  38. package/dist/server/Email.d.ts.map +1 -1
  39. package/dist/server/Email.js +7 -1
  40. package/dist/server/Email.js.map +1 -1
  41. package/dist/server/Logging.d.ts +5 -8
  42. package/dist/server/Logging.d.ts.map +1 -1
  43. package/dist/server/Logging.js +21 -97
  44. package/dist/server/Logging.js.map +1 -1
  45. package/dist/server/adapter/workers/cf-types.d.ts +7 -0
  46. package/dist/server/adapter/workers/cf-types.d.ts.map +1 -1
  47. package/dist/server/adapter/workers/createWorkersApp.js +1 -0
  48. package/dist/server/adapter/workers/createWorkersApp.js.map +1 -1
  49. package/dist/server/auth/magicLinkProvider.d.ts.map +1 -1
  50. package/dist/server/auth/magicLinkProvider.js +55 -6
  51. package/dist/server/auth/magicLinkProvider.js.map +1 -1
  52. package/dist/server/auth/workersMagicLink.d.ts.map +1 -1
  53. package/dist/server/auth/workersMagicLink.js +117 -13
  54. package/dist/server/auth/workersMagicLink.js.map +1 -1
  55. package/dist/server/bootstrap.d.ts +5 -21
  56. package/dist/server/bootstrap.d.ts.map +1 -1
  57. package/dist/server/bootstrap.js +5 -27
  58. package/dist/server/bootstrap.js.map +1 -1
  59. package/dist/shared/DB.d.ts +0 -9
  60. package/dist/shared/DB.d.ts.map +1 -1
  61. package/dist/shared/DB.js +46 -10
  62. package/dist/shared/DB.js.map +1 -1
  63. package/dist/vite/index.d.ts +0 -1
  64. package/dist/vite/index.d.ts.map +1 -1
  65. package/dist/vite/index.js +0 -1
  66. package/dist/vite/index.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/cli/dev.ts +0 -4
  69. package/src/cli/doctor.ts +31 -63
  70. package/src/cli/index.ts +9 -81
  71. package/src/cli/migrate.ts +0 -4
  72. package/src/cli/schemaGen.ts +0 -4
  73. package/src/cli/schemaStatus.ts +0 -4
  74. package/src/cli/serverLogQueryApi.ts +53 -136
  75. package/src/cli/version.ts +1 -1
  76. package/src/client/auth/MagicLinkCallback.tsx +10 -2
  77. package/src/client/auth/MagicLinkForm.tsx +7 -1
  78. package/src/server/App.ts +1 -2
  79. package/src/server/Email.ts +7 -1
  80. package/src/server/Logging.ts +21 -97
  81. package/src/server/adapter/workers/cf-types.ts +8 -0
  82. package/src/server/adapter/workers/createWorkersApp.ts +1 -0
  83. package/src/server/auth/magicLinkProvider.ts +61 -6
  84. package/src/server/auth/workersMagicLink.ts +117 -13
  85. package/src/server/bootstrap.ts +5 -29
  86. package/src/shared/DB.ts +46 -17
  87. package/src/vite/index.ts +0 -1
  88. package/.pgdata/base/1/pg_internal.init +0 -0
  89. package/.pgdata/base/17702/pg_internal.init +0 -0
  90. package/.pgdata/base/5/pg_internal.init +0 -0
  91. package/dist/cli/devTunnel.d.ts +0 -37
  92. package/dist/cli/devTunnel.d.ts.map +0 -1
  93. package/dist/cli/devTunnel.js +0 -470
  94. package/dist/cli/devTunnel.js.map +0 -1
  95. package/dist/cli/ensureInfra.d.ts +0 -9
  96. package/dist/cli/ensureInfra.d.ts.map +0 -1
  97. package/dist/cli/ensureInfra.js +0 -86
  98. package/dist/cli/ensureInfra.js.map +0 -1
  99. package/dist/cli/sidecar.d.ts +0 -21
  100. package/dist/cli/sidecar.d.ts.map +0 -1
  101. package/dist/cli/sidecar.js +0 -294
  102. package/dist/cli/sidecar.js.map +0 -1
  103. package/dist/cli/sidecarHealth.d.ts +0 -13
  104. package/dist/cli/sidecarHealth.d.ts.map +0 -1
  105. package/dist/cli/sidecarHealth.js +0 -42
  106. package/dist/cli/sidecarHealth.js.map +0 -1
  107. package/dist/cli/sidecarProtocol.d.ts +0 -41
  108. package/dist/cli/sidecarProtocol.d.ts.map +0 -1
  109. package/dist/cli/sidecarProtocol.js +0 -61
  110. package/dist/cli/sidecarProtocol.js.map +0 -1
  111. package/dist/vite/tunnelPlugin.d.ts +0 -10
  112. package/dist/vite/tunnelPlugin.d.ts.map +0 -1
  113. package/dist/vite/tunnelPlugin.js +0 -30
  114. package/dist/vite/tunnelPlugin.js.map +0 -1
  115. package/src/cli/devTunnel.ts +0 -589
  116. package/src/cli/ensureInfra.ts +0 -90
  117. package/src/cli/sidecar.ts +0 -350
  118. package/src/cli/sidecarHealth.ts +0 -58
  119. package/src/cli/sidecarProtocol.ts +0 -68
  120. package/src/vite/tunnelPlugin.ts +0 -32
@@ -102,13 +102,37 @@ const MAX_REQUESTS_PER_EMAIL_PER_HOUR = 5;
102
102
 
103
103
  const requestBodySchema = z.object({
104
104
  email: z.string().email().max(254),
105
+ returnTo: z.string().max(2000).optional(),
105
106
  });
106
107
 
107
108
  const verifyBodySchema = z.object({
108
109
  id: z.string().min(1).max(128),
109
110
  sig: z.string().min(1).max(128),
111
+ returnTo: z.string().max(2000).optional(),
110
112
  });
111
113
 
114
+ /** Same-origin redirect guard. See `workersMagicLink.ts` for rationale. */
115
+ function validateReturnTo(
116
+ returnTo: string | undefined,
117
+ requestHost: string,
118
+ ): string {
119
+ if (!returnTo || returnTo.length > 2000) return '/';
120
+ if (/^[a-z][a-z0-9+\-.]*:/i.test(returnTo)) {
121
+ try {
122
+ const u = new URL(returnTo);
123
+ if (u.protocol !== 'https:' && u.protocol !== 'http:') return '/';
124
+ if (u.host.toLowerCase() !== requestHost.toLowerCase()) return '/';
125
+ if (u.pathname.startsWith('/auth/magic-link/')) return '/';
126
+ return `${u.pathname}${u.search}${u.hash}`;
127
+ } catch {
128
+ return '/';
129
+ }
130
+ }
131
+ if (!returnTo.startsWith('/') || returnTo.startsWith('//')) return '/';
132
+ if (returnTo.startsWith('/auth/magic-link/')) return '/';
133
+ return returnTo;
134
+ }
135
+
112
136
  /**
113
137
  * Magic-link provider (Mode B default). Issues a single-use signed token
114
138
  * over email; verification mints a local `auth_token` JWT cookie.
@@ -206,7 +230,21 @@ export function createMagicLinkProvider(
206
230
  });
207
231
 
208
232
  const base = getAppPublicUrl();
209
- const url = `${base}/auth/magic-link/verify?id=${encodeURIComponent(tokenId)}&sig=${encodeURIComponent(sig)}`;
233
+ let baseHost = '';
234
+ try {
235
+ baseHost = new URL(base).host;
236
+ } catch {
237
+ baseHost = req.get('host') ?? '';
238
+ }
239
+ const safeReturnTo = validateReturnTo(
240
+ parsed.data.returnTo,
241
+ baseHost,
242
+ );
243
+ const returnToParam =
244
+ safeReturnTo === '/'
245
+ ? ''
246
+ : `&returnTo=${encodeURIComponent(safeReturnTo)}`;
247
+ const url = `${base}/auth/magic-link/verify?id=${encodeURIComponent(tokenId)}&sig=${encodeURIComponent(sig)}${returnToParam}`;
210
248
 
211
249
  const tpl = magicLinkEmail(url, appName, expiresMin);
212
250
  await emailSend({
@@ -234,22 +272,35 @@ export function createMagicLinkProvider(
234
272
  (req: Request, res: Response) => {
235
273
  const id = String(req.query['id'] ?? '');
236
274
  const sig = String(req.query['sig'] ?? '');
275
+ const safeReturnTo = validateReturnTo(
276
+ req.query['returnTo'] != null
277
+ ? String(req.query['returnTo'])
278
+ : undefined,
279
+ req.get('host') ?? '',
280
+ );
237
281
  // Inline JSON-encode to prevent <script>-context XSS; even though
238
282
  // these values are signed/short, defence-in-depth.
239
283
  const idJson = JSON.stringify(id);
240
284
  const sigJson = JSON.stringify(sig);
285
+ const returnToJson = JSON.stringify(safeReturnTo);
241
286
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
242
287
  res.send(`<!doctype html><html><head><meta charset="utf-8"><title>Signing in…</title></head><body style="font-family:system-ui,sans-serif;padding:24px;text-align:center"><p>Signing you in…</p><script>
243
288
  (async function(){
289
+ var returnTo = ${returnToJson};
244
290
  try {
245
- const r = await fetch('/auth/magic-link/verify', {
291
+ var r = await fetch('/auth/magic-link/verify', {
246
292
  method: 'POST',
247
293
  headers: {'Content-Type': 'application/json'},
248
294
  credentials: 'same-origin',
249
- body: JSON.stringify({ id: ${idJson}, sig: ${sigJson} }),
295
+ body: JSON.stringify({ id: ${idJson}, sig: ${sigJson}, returnTo: returnTo }),
250
296
  });
251
- if (r.ok) { window.location.replace('/'); }
252
- else { document.body.innerText = 'This sign-in link is invalid or has expired.'; }
297
+ if (r.ok) {
298
+ var data = await r.json().catch(function(){ return {}; });
299
+ var dest = (data && data.returnTo) || returnTo || '/';
300
+ window.location.replace(dest);
301
+ } else {
302
+ document.body.innerText = 'This sign-in link is invalid or has expired.';
303
+ }
253
304
  } catch (e) { document.body.innerText = 'Sign-in failed. Please try again.'; }
254
305
  })();
255
306
  </script></body></html>`);
@@ -321,7 +372,11 @@ export function createMagicLinkProvider(
321
372
  maxAge: COOKIE_MAX_AGE_SECONDS * 1000,
322
373
  path: '/',
323
374
  });
324
- res.json({ ok: true, token });
375
+ const safeReturnTo = validateReturnTo(
376
+ parsed.data.returnTo,
377
+ req.get('host') ?? '',
378
+ );
379
+ res.json({ ok: true, token, returnTo: safeReturnTo });
325
380
  } catch (err) {
326
381
  console.error('[Auth] magic-link verify failed', err);
327
382
  res.status(401).json({ error: 'Invalid link' });
@@ -31,13 +31,53 @@ const MAX_REQUESTS_PER_EMAIL_PER_HOUR = 5;
31
31
 
32
32
  const requestBodySchema = z.object({
33
33
  email: z.string().email().max(254),
34
+ /**
35
+ * Optional URL the client wants to land on after sign-in. Must be
36
+ * same-origin — full URLs to other hosts are silently downgraded to
37
+ * `/` by `validateReturnTo`. Sent by `MagicLinkForm` as
38
+ * `window.location.href` so the user resumes wherever they were.
39
+ */
40
+ returnTo: z.string().max(2000).optional(),
34
41
  });
35
42
 
36
43
  const verifyBodySchema = z.object({
37
44
  id: z.string().min(1).max(128),
38
45
  sig: z.string().min(1).max(128),
46
+ returnTo: z.string().max(2000).optional(),
39
47
  });
40
48
 
49
+ /**
50
+ * Same-origin redirect guard. Returns a safe path (always starts with
51
+ * `/`) — `/` when the input is missing, off-origin, or points back at
52
+ * `/auth/magic-link/*` (loop prevention). The verify URL inside the
53
+ * email is signed (HMAC binds id+email+expiresAt), so a tampered
54
+ * returnTo can't be used to bypass auth, but a same-origin guard still
55
+ * matters to prevent post-login open redirects.
56
+ */
57
+ function validateReturnTo(
58
+ returnTo: string | undefined,
59
+ requestHost: string,
60
+ ): string {
61
+ if (!returnTo || returnTo.length > 2000) return '/';
62
+ // Absolute URL: must be http(s) AND same host.
63
+ if (/^[a-z][a-z0-9+\-.]*:/i.test(returnTo)) {
64
+ try {
65
+ const u = new URL(returnTo);
66
+ if (u.protocol !== 'https:' && u.protocol !== 'http:') return '/';
67
+ if (u.host.toLowerCase() !== requestHost.toLowerCase()) return '/';
68
+ if (u.pathname.startsWith('/auth/magic-link/')) return '/';
69
+ return `${u.pathname}${u.search}${u.hash}`;
70
+ } catch {
71
+ return '/';
72
+ }
73
+ }
74
+ // Relative — accept only paths that start with `/` (block protocol-
75
+ // relative `//evil.com` URLs and bare path-traversal).
76
+ if (!returnTo.startsWith('/') || returnTo.startsWith('//')) return '/';
77
+ if (returnTo.startsWith('/auth/magic-link/')) return '/';
78
+ return returnTo;
79
+ }
80
+
41
81
  export interface WorkersMagicLinkConfig {
42
82
  /** Magic-link lifetime in minutes (1..1440). */
43
83
  expiresMin: number;
@@ -223,16 +263,66 @@ export function registerWorkersMagicLinkRoutes(
223
263
  });
224
264
 
225
265
  const base = getAppPublicUrl(c, config.publicAppUrl);
226
- const url = `${base}/auth/magic-link/verify?id=${encodeURIComponent(tokenId)}&sig=${encodeURIComponent(sig)}`;
266
+ // Validate returnTo against the public origin so a malicious
267
+ // `returnTo` (sent in the request body) can't ride along through
268
+ // the email and steal the post-login navigation. The validation
269
+ // strips off-origin URLs to `/` silently.
270
+ let baseHost = '';
271
+ try {
272
+ baseHost = new URL(base).host;
273
+ } catch {
274
+ baseHost = c.req.header('host') ?? '';
275
+ }
276
+ const safeReturnTo = validateReturnTo(parsed.data.returnTo, baseHost);
277
+ const returnToParam =
278
+ safeReturnTo === '/'
279
+ ? ''
280
+ : `&returnTo=${encodeURIComponent(safeReturnTo)}`;
281
+ const url = `${base}/auth/magic-link/verify?id=${encodeURIComponent(tokenId)}&sig=${encodeURIComponent(sig)}${returnToParam}`;
282
+
283
+ const debugMode = process.env['UGLY_APP_MAGIC_LINK_DEBUG'] === '1';
284
+ // Debug mode: log the link to the worker console so it's grep-able
285
+ // from `wrangler tail`. Also returned in the response below.
286
+ // ALWAYS off in prod — gated on UGLY_APP_MAGIC_LINK_DEBUG and the
287
+ // self auth-mode binding, never on by default.
288
+ if (debugMode) {
289
+ // eslint-disable-next-line no-console
290
+ console.log(
291
+ `[Auth] DEBUG magic-link for ${email}: ${url}`,
292
+ );
293
+ }
227
294
 
228
295
  const tpl = magicLinkEmail(url, appName, expiresMin);
229
- await emailSend({
230
- to: email,
231
- subject: tpl.subject,
232
- html: tpl.html,
233
- text: tpl.text,
234
- id: 'magic-link',
235
- });
296
+ let emailError: unknown = null;
297
+ try {
298
+ await emailSend({
299
+ to: email,
300
+ subject: tpl.subject,
301
+ html: tpl.html,
302
+ text: tpl.text,
303
+ id: 'magic-link',
304
+ });
305
+ } catch (err) {
306
+ emailError = err;
307
+ // eslint-disable-next-line no-console
308
+ console.error('[Auth] magic-link email send failed', err);
309
+ }
310
+
311
+ if (debugMode) {
312
+ // Debug-only response shape — magic link surfaced directly so
313
+ // you can drive the verify step from a test harness without a
314
+ // working email proxy or sending domain. Anti-enum still
315
+ // covers the prod path; this branch only runs when the
316
+ // operator opts in.
317
+ return c.json({
318
+ ok: true,
319
+ debug: {
320
+ link: url,
321
+ emailDelivered: !emailError,
322
+ emailError: emailError ? String(emailError) : null,
323
+ },
324
+ });
325
+ }
236
326
  } catch (err) {
237
327
  // eslint-disable-next-line no-console
238
328
  console.error('[Auth] magic-link request failed', err);
@@ -246,19 +336,31 @@ export function registerWorkersMagicLinkRoutes(
246
336
  }
247
337
  const id = c.req.query('id') ?? '';
248
338
  const sig = c.req.query('sig') ?? '';
339
+ // Re-validate the URL-borne returnTo at GET time too. The link can
340
+ // be tampered between email-render and click, so we don't trust
341
+ // whatever shows up here without re-checking origin.
342
+ const host = c.req.header('host') ?? '';
343
+ const safeReturnTo = validateReturnTo(c.req.query('returnTo'), host);
249
344
  const idJson = JSON.stringify(id);
250
345
  const sigJson = JSON.stringify(sig);
346
+ const returnToJson = JSON.stringify(safeReturnTo);
251
347
  const html = `<!doctype html><html><head><meta charset="utf-8"><title>Signing in…</title></head><body style="font-family:system-ui,sans-serif;padding:24px;text-align:center"><p>Signing you in…</p><script>
252
348
  (async function(){
349
+ var returnTo = ${returnToJson};
253
350
  try {
254
- const r = await fetch('/auth/magic-link/verify', {
351
+ var r = await fetch('/auth/magic-link/verify', {
255
352
  method: 'POST',
256
353
  headers: {'Content-Type': 'application/json'},
257
354
  credentials: 'same-origin',
258
- body: JSON.stringify({ id: ${idJson}, sig: ${sigJson} }),
355
+ body: JSON.stringify({ id: ${idJson}, sig: ${sigJson}, returnTo: returnTo }),
259
356
  });
260
- if (r.ok) { window.location.replace('/'); }
261
- else { document.body.innerText = 'This sign-in link is invalid or has expired.'; }
357
+ if (r.ok) {
358
+ var data = await r.json().catch(function(){ return {}; });
359
+ var dest = (data && data.returnTo) || returnTo || '/';
360
+ window.location.replace(dest);
361
+ } else {
362
+ document.body.innerText = 'This sign-in link is invalid or has expired.';
363
+ }
262
364
  } catch (e) { document.body.innerText = 'Sign-in failed. Please try again.'; }
263
365
  })();
264
366
  </script></body></html>`;
@@ -309,7 +411,9 @@ export function registerWorkersMagicLinkRoutes(
309
411
 
310
412
  const token = await mintSessionJwt(userId, secret);
311
413
  c.header('Set-Cookie', buildAuthCookie(token));
312
- return c.json({ ok: true, token });
414
+ const host = c.req.header('host') ?? '';
415
+ const safeReturnTo = validateReturnTo(parsed.data.returnTo, host);
416
+ return c.json({ ok: true, token, returnTo: safeReturnTo });
313
417
  } catch (err) {
314
418
  // eslint-disable-next-line no-console
315
419
  console.error('[Auth] magic-link verify failed', err);
@@ -1,39 +1,15 @@
1
1
  /**
2
- * bootstrapInfra — the supported way to wire up the Postgres + Storage
3
- * connections from any script that imports `ugly-app/server`. Wraps
4
- * the two-step dance every seed / migration / import script needs:
2
+ * bootstrapInfra — supported entrypoint for wiring up the Postgres pool
3
+ * from any script that imports `ugly-app/server`. Touches the singleton
4
+ * pg.Pool to surface config errors early. The pool itself is lazily
5
+ * created on first query.
5
6
  *
6
- * 1. `ensureInfra()` reads `.uglyapp`, logs in if needed, sets
7
- * DATABASE_URL / R2_* env vars for this process only. No `.env`
8
- * file is ever touched.
9
- * 2. `initPg()` — touches the singleton pg.Pool to surface config
10
- * errors early. The pool itself is lazily created on first query.
11
- *
12
- * After this returns, `pgQuery`, `createTypedDB`, `storage`, etc. all
13
- * work as if the script were running inside `ugly-app dev`.
14
- *
15
- * Idempotent: if the pool is already open (e.g. when a test or another
16
- * script already bootstrapped the process), the call is a no-op. Safe
17
- * to call at the top of every script that touches data.
7
+ * Idempotent: a no-op if the pool is already open.
18
8
  */
19
9
 
20
10
  import { initPg, isPgConnected } from './NodePool.js';
21
11
 
22
- /**
23
- * Ensure Postgres infrastructure is initialised. Safe to call multiple
24
- * times. Throws on unrecoverable config errors (no DATABASE_URL,
25
- * login rejected, etc.). The thrown error comes straight from
26
- * `ensureInfra` so the caller sees the real reason.
27
- */
28
12
  export async function bootstrapInfra(): Promise<void> {
29
13
  if (isPgConnected()) return;
30
-
31
- // ensureInfra lives under ../cli/ so we pull it in via dynamic
32
- // import. That keeps the static import graph of `ugly-app/server`
33
- // clean — nothing in the server runtime has to depend on the CLI
34
- // layer unless user code asks for it via bootstrapInfra.
35
- const { ensureInfra } = await import('../cli/ensureInfra.js');
36
- await ensureInfra();
37
-
38
14
  await initPg();
39
15
  }
package/src/shared/DB.ts CHANGED
@@ -410,8 +410,6 @@ export const ErrorLogDocSchema = z.object({
410
410
  sessionId: z.string().optional(),
411
411
  buildId: z.string().optional(),
412
412
  projectId: z.string().optional(),
413
- /** Set on rows captured via a dev-tunnel data-proxy connection. Null/missing for deployed-server rows. */
414
- devTunnelId: z.string().nullable().optional(),
415
413
  }),
416
414
  message: z.string(),
417
415
  stack: z.string().nullable().optional(),
@@ -449,13 +447,6 @@ export interface ErrorLogListInput {
449
447
  type?: ErrorLogType;
450
448
  userId?: string;
451
449
  projectId?: string;
452
- /**
453
- * Filter by dev-tunnel ownership.
454
- * - String → only rows from that dev's tunnel sessions.
455
- * - 'none' → only rows from deployed servers (devTunnelId IS NULL).
456
- * - Omitted → no filter applied.
457
- */
458
- devTunnelId?: string | 'none';
459
450
  startDate?: number;
460
451
  endDate?: number;
461
452
  cursor?: number;
@@ -501,8 +492,6 @@ export const PerfLogSchema = z.object({
501
492
  operation: z.string(),
502
493
  durationMs: z.number(),
503
494
  userId: z.string(),
504
- /** Set on rows captured via a dev-tunnel data-proxy connection. Null/missing for deployed-server rows. */
505
- devTunnelId: z.string().nullable().optional(),
506
495
  });
507
496
  export type PerfLog = InferDocType<typeof PerfLogSchema>;
508
497
 
@@ -627,13 +616,53 @@ export interface EventLog {
627
616
  // Builtin collection defs (no user — apps define their own)
628
617
  // ---------------------------------------------------------------------------
629
618
 
630
- // All logging collections (errorLog, perfLog, eventLog, eventCounter) are
631
- // proxied to ugly.bot no local tables needed.
632
- //
633
- // Auth collections (authMagicLink, authIdentity) are local: they back the
634
- // app-issued session flow when .uglyapp `auth.mode === 'self'`. With
635
- // `auth.mode === 'uglybot'` (the default) these tables stay empty.
619
+ // Built-in collections owned by every ugly-app project:
620
+ // - errorLog / perfLog / feedbackReport: runtime diagnostics, written by
621
+ // Logging.ts and surfaced via the `ugly-app errors` / `perf` / `feedback`
622
+ // CLI commands and Studio panels.
623
+ // - authMagicLink / authIdentity: local back-store for the app-issued
624
+ // session flow when .uglyapp `auth.mode === 'self'`. With
625
+ // `auth.mode === 'uglybot'` (the default) these tables stay empty.
636
626
  export const BUILTIN_DEFS = defineCollections({
627
+ errorLog: {
628
+ schema: ErrorLogDocSchema,
629
+ meta: {
630
+ cache: false,
631
+ trackable: false,
632
+ public: false,
633
+ cascadeFrom: null,
634
+ },
635
+ indexes: [
636
+ { fields: { 'meta.level': 1 } },
637
+ { fields: { 'meta.userId': 1 } },
638
+ ],
639
+ },
640
+ perfLog: {
641
+ schema: PerfLogSchema,
642
+ meta: {
643
+ cache: false,
644
+ trackable: false,
645
+ public: false,
646
+ cascadeFrom: null,
647
+ },
648
+ indexes: [
649
+ { fields: { operation: 1 } },
650
+ { fields: { userId: 1 } },
651
+ ],
652
+ },
653
+ feedbackReport: {
654
+ schema: FeedbackReportDocSchema,
655
+ meta: {
656
+ cache: false,
657
+ trackable: false,
658
+ public: false,
659
+ cascadeFrom: null,
660
+ },
661
+ indexes: [
662
+ { fields: { userId: 1 } },
663
+ { fields: { status: 1 } },
664
+ ],
665
+ },
637
666
  authMagicLink: {
638
667
  schema: z.object({
639
668
  email: z.string(),
package/src/vite/index.ts CHANGED
@@ -1,2 +1 @@
1
1
  export { dataSourcePlugin } from './dataSourcePlugin.js';
2
- export { tunnelPlugin } from './tunnelPlugin.js';
Binary file
Binary file
Binary file
@@ -1,37 +0,0 @@
1
- /**
2
- * devTunnel.ts
3
- *
4
- * Combined dev tunnel client: multiplexes infrastructure TCP ports (Postgres,
5
- * NATS, S3, Qdrant) over binary frames AND handles proxy tunnel HTTP/WS relay
6
- * for public dev URLs — all on a single WebSocket to /dev-tunnel.
7
- *
8
- * Binary frames = infra tunnel (5-byte header + TCP payload)
9
- * Text frames = proxy tunnel (JSON messages for HTTP/WS relay)
10
- */
11
- import type { SidecarPorts } from './sidecarHealth.js';
12
- export interface StartDevTunnelOpts {
13
- projectId: string;
14
- token: string;
15
- /** Defaults to UGLY_BOT_WS_URL */
16
- serverUrl?: string;
17
- /** When set, enables proxy tunnel relay to this local port */
18
- vitePort?: number;
19
- /**
20
- * Optional studio session id (`ws_x:sess_y`). When set, the server
21
- * scopes the proxy subdomain to this session so two concurrent dev
22
- * servers on the same project don't collide on the same registration.
23
- */
24
- sessionId?: string;
25
- }
26
- export interface DevTunnelResult extends SidecarPorts {
27
- /** Public subdomain if proxy tunnel registered (e.g. 'proxy-user-myapp') */
28
- subdomain?: string;
29
- }
30
- /**
31
- * Start the dev tunnel: opens WSS connection and binds ephemeral TCP ports.
32
- * If vitePort is provided, also registers a proxy tunnel for public URL access.
33
- */
34
- export declare function startDevTunnel(opts: StartDevTunnelOpts): Promise<DevTunnelResult>;
35
- /** Stop the dev tunnel: close WS, all TCP servers and sockets. */
36
- export declare function stopDevTunnel(): void;
37
- //# sourceMappingURL=devTunnel.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"devTunnel.d.ts","sourceRoot":"","sources":["../../src/cli/devTunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUvD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8BD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CA0DvF;AAwaD,kEAAkE;AAClE,wBAAgB,aAAa,IAAI,IAAI,CA6BpC"}