-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvite.config.ts
More file actions
159 lines (152 loc) · 5.79 KB
/
Copy pathvite.config.ts
File metadata and controls
159 lines (152 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import type { Plugin } from "vite";
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { nitro } from "nitro/vite";
// @ts-expect-error JS plugin alongside the TS vite config
import { grokPwaPlugin } from "./scripts/grok-pwa-plugin.mjs";
/**
* Finish PGLite bootstrap during dev-server setup (before traffic). Vite awaits
* async `configureServer` hooks. Production: `src/lib/db` kicks `ensureDbReady`
* on import.
*/
function pgliteBootstrapPlugin(): Plugin {
return {
name: "app-builder:pglite-bootstrap",
apply: "serve",
async configureServer(server) {
try {
const mod = (await server.ssrLoadModule("/src/lib/db.ts")) as {
ensureDbReady?: () => Promise<void>;
};
if (typeof mod.ensureDbReady === "function") {
await mod.ensureDbReady();
}
} catch (err) {
console.error("[app-builder] DB bootstrap failed:", err);
throw err;
}
},
};
}
/**
* Live-preview OAuth popup — handled HERE so the agent never has to create a
* `/auth/popup` route (and cannot break it by scaffolding a React page that
* paints the full app shell in the popup).
*
* `signIn` (client.ts) opens `/auth/popup?providerId=…` in a top-level window.
* This middleware runs before TanStack Start, calls `handleAuthPopupRequest`,
* and returns the 302 / completion HTML. Deployed apps do not use the popup
* (full-page OAuth redirect), so `apply: "serve"` is enough.
*/
function authPopupPlugin(): Plugin {
return {
name: "app-builder:auth-popup",
apply: "serve",
configureServer(server) {
// Register immediately (not in a returned post-hook) so we run BEFORE
// TanStack Start / the SPA HTML fallback. A model-authored
// `src/routes/auth/popup.tsx` React page must never win this path.
server.middlewares.use(async (req, res, next) => {
try {
const rawUrl = req.url ?? "";
const pathOnly = rawUrl.split("?", 1)[0] ?? "";
if (pathOnly !== "/auth/popup") {
next();
return;
}
if ((req.method ?? "GET").toUpperCase() !== "GET") {
res.statusCode = 405;
res.setHeader("content-type", "text/plain; charset=utf-8");
res.end("Method Not Allowed");
return;
}
const host = String(
req.headers["x-forwarded-host"] ?? req.headers.host ?? "localhost:8080",
);
const proto = String(
req.headers["x-forwarded-proto"] ??
((req.socket as { encrypted?: boolean } | undefined)?.encrypted ? "https" : "http"),
);
const requestHeaders = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
if (value === undefined) continue;
if (Array.isArray(value)) {
for (const v of value) requestHeaders.append(key, v);
} else {
requestHeaders.set(key, value);
}
}
// Ensure Host is the public preview host so Better Auth's dynamic
// baseURL / redirect_uri match the popup origin.
if (!requestHeaders.has("host")) requestHeaders.set("host", host);
const request = new Request(`${proto}://${host}${rawUrl}`, {
method: "GET",
headers: requestHeaders,
});
const mod = (await server.ssrLoadModule("/src/lib/auth/popup.server.ts")) as {
handleAuthPopupRequest: (req: Request) => Promise<Response>;
};
const response = await mod.handleAuthPopupRequest(request);
res.statusCode = response.status;
// Preserve multiple Set-Cookie headers (OAuth state + session).
const setCookies =
typeof response.headers.getSetCookie === "function"
? response.headers.getSetCookie()
: [];
response.headers.forEach((value, key) => {
if (key.toLowerCase() === "set-cookie") return;
res.setHeader(key, value);
});
for (const cookie of setCookies) {
res.appendHeader("set-cookie", cookie);
}
const body = Buffer.from(await response.arrayBuffer());
res.end(body);
} catch (err) {
console.error("[app-builder] /auth/popup handler failed:", err);
if (!res.headersSent) {
res.statusCode = 500;
res.setHeader("content-type", "text/plain; charset=utf-8");
res.end("auth popup failed");
}
}
});
},
};
}
// `0.0.0.0:8080` is the live-preview contract — don't change host/port.
// Keep `nitro` gated to `build` (the Vercel deploy target): enabled in dev it
// opens a second dev-server port, which breaks the single-port preview.
// The dev server starts once `src/router.tsx` and `src/routes/` exist — see
// AGENTS.md § "First scaffold".
export default defineConfig(({ command }) => ({
server: {
host: "0.0.0.0",
port: 8080,
strictPort: true,
},
resolve: { tsconfigPaths: true },
plugins: [
pgliteBootstrapPlugin(),
// Before tanstackStart so /auth/popup never falls through to the SPA.
authPopupPlugin(),
// PWA head + ?install=1 tutorial page; runs before Start/Nitro.
grokPwaPlugin(),
tailwindcss(),
tanstackStart(),
...(command === "build"
? [
nitro({
preset: "vercel",
// Auto-registers server/middleware/* (the PWA install page +
// manifest + head-tag middleware). Nitro v3 defaults serverDir to
// false, so removing this silently unwires /?install=1 on deploys.
serverDir: "./server",
}),
]
: []),
viteReact(),
],
}));