@helloleo/vite-config 0.1.0 → 0.1.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.
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function devSsrErrorLogger(): Plugin;
3
+ export declare function devServerFnErrorLogger(): Plugin;
@@ -0,0 +1,6 @@
1
+ import { type Plugin, type UserConfig } from "vite";
2
+ export interface HelloLeoRouterConfigOptions {
3
+ vite?: UserConfig;
4
+ plugins?: Plugin[];
5
+ }
6
+ export declare function defineConfig(options?: HelloLeoRouterConfigOptions): () => Promise<UserConfig>;
package/dist/router.js ADDED
@@ -0,0 +1,31 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/router.ts
5
+ import { mergeConfig } from "vite";
6
+ function defineConfig(options = {}) {
7
+ return async () => {
8
+ const internalPlugins = [];
9
+ const tailwindcss = (await import("@tailwindcss/vite")).default;
10
+ internalPlugins.push(...tailwindcss());
11
+ const { tanstackRouter } = await import("@tanstack/router-plugin/vite");
12
+ internalPlugins.push(tanstackRouter({ target: "react", autoCodeSplitting: true }));
13
+ const viteReact = (await import("@vitejs/plugin-react")).default;
14
+ internalPlugins.push(...viteReact());
15
+ let config = {
16
+ resolve: { tsconfigPaths: true },
17
+ server: {
18
+ allowedHosts: true,
19
+ hmr: { clientPort: 443, protocol: "wss" }
20
+ },
21
+ optimizeDeps: { entries: ["index.html", "./src/**/*.{ts,tsx}"] },
22
+ plugins: [...internalPlugins, ...options.plugins ?? []]
23
+ };
24
+ if (options.vite)
25
+ config = mergeConfig(config, options.vite);
26
+ return config;
27
+ };
28
+ }
29
+ export {
30
+ defineConfig
31
+ };
@@ -0,0 +1,2 @@
1
+ export declare function isSandboxEnvironment(): boolean;
2
+ export type Target = "workerd" | "sandbox";
@@ -0,0 +1,11 @@
1
+ import { type Plugin, type UserConfig } from "vite";
2
+ export interface HelloLeoConfigOptions {
3
+ vite?: UserConfig;
4
+ plugins?: Plugin[];
5
+ ssrErrorLogger?: boolean;
6
+ serverFnErrorLogger?: boolean;
7
+ }
8
+ export declare function defineConfig(options?: HelloLeoConfigOptions): (env: {
9
+ command: string;
10
+ mode: string;
11
+ }) => Promise<UserConfig>;
package/dist/start.js ADDED
@@ -0,0 +1,244 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/start.ts
5
+ import { loadEnv, mergeConfig } from "vite";
6
+
7
+ // src/dev-error-loggers.ts
8
+ var SSR_CAPTURE_KEY = "__HELLOLEO_CAPTURE_SSR_ERROR__";
9
+ var HANDLER_RAN_KEY = "__HELLOLEO_HANDLER_RAN__";
10
+ var HMR_SEND_KEY = "__HELLOLEO_SERVER_FN_HMR_SEND__";
11
+ var BUILD_ERROR_MESSAGE = /transform failed with \d+ error|failed to parse source/i;
12
+ function isBuildError(error) {
13
+ if (typeof error === "string")
14
+ return BUILD_ERROR_MESSAGE.test(error);
15
+ if (!error || typeof error !== "object")
16
+ return false;
17
+ const e = error;
18
+ if (typeof e.plugin === "string" && (e.plugin.startsWith("vite:") || e.plugin.startsWith("esbuild:")))
19
+ return true;
20
+ if (e.frame != null && e.loc != null)
21
+ return true;
22
+ if (Array.isArray(e.errors) && e.errors.length > 0 && e.errors.every((x) => x != null && typeof x === "object" && ("text" in x) && ("location" in x))) {
23
+ return true;
24
+ }
25
+ const message = typeof e.message === "string" ? e.message : "";
26
+ return BUILD_ERROR_MESSAGE.test(message);
27
+ }
28
+ function devSsrErrorLogger() {
29
+ let lastCapture;
30
+ const CAPTURE_TTL_MS = 5000;
31
+ const handlerRanFlag = Symbol("helloleoHandlerRan");
32
+ const capture = (error) => {
33
+ lastCapture = { error, at: Date.now() };
34
+ };
35
+ const consumeCapture = () => {
36
+ if (!lastCapture)
37
+ return;
38
+ if (Date.now() - lastCapture.at > CAPTURE_TTL_MS) {
39
+ lastCapture = undefined;
40
+ return;
41
+ }
42
+ const { error } = lastCapture;
43
+ lastCapture = undefined;
44
+ return error;
45
+ };
46
+ return {
47
+ name: "helloleo:dev-ssr-error-logger",
48
+ apply: "serve",
49
+ configureServer(server) {
50
+ const g = globalThis;
51
+ g[SSR_CAPTURE_KEY] = capture;
52
+ g[HANDLER_RAN_KEY] = (event) => {
53
+ const res = event?.node?.res;
54
+ if (res)
55
+ res[handlerRanFlag] = true;
56
+ };
57
+ if (typeof g.addEventListener === "function") {
58
+ const add = g.addEventListener;
59
+ add("error", (e) => capture(e.error ?? e));
60
+ add("unhandledrejection", (e) => capture(e.reason));
61
+ }
62
+ const onUnhandledRejection = (reason) => capture(reason);
63
+ process.on("unhandledRejection", onUnhandledRejection);
64
+ server.httpServer?.once("close", () => {
65
+ process.off("unhandledRejection", onUnhandledRejection);
66
+ });
67
+ server.middlewares.use((req, res, next) => {
68
+ let handled = false;
69
+ const handleErrorResponse = () => {
70
+ if (handled || res.statusCode < 500)
71
+ return;
72
+ handled = true;
73
+ const captured = consumeCapture();
74
+ const ranHandler = Boolean(res[handlerRanFlag]);
75
+ let source;
76
+ if (!ranHandler)
77
+ source = "vite";
78
+ else if (isBuildError(captured))
79
+ source = "build";
80
+ else
81
+ source = "app";
82
+ if (!res.headersSent) {
83
+ try {
84
+ res.setHeader("x-helloleo-error-source", source);
85
+ } catch {}
86
+ }
87
+ const where = [req.method, req.url].filter(Boolean).join(" ") || "the request";
88
+ let data;
89
+ if (captured instanceof Error) {
90
+ data = { name: captured.name, message: captured.message, stack: captured.stack };
91
+ } else if (typeof captured === "string" && captured.length > 0) {
92
+ data = { name: "Error", message: captured };
93
+ } else if (ranHandler) {
94
+ data = {
95
+ message: `The app returned ${res.statusCode} while handling ${where}. Handled by a route/error boundary, so no stack was captured — check the failing loader/route code.`
96
+ };
97
+ } else {
98
+ data = {
99
+ message: `Dev server returned ${res.statusCode} for ${where} before the app handler ran — usually a Vite build/transform error. Check the dev server output.`
100
+ };
101
+ }
102
+ try {
103
+ server.ws.send({ type: "custom", event: "server-ssr-error", data });
104
+ } catch {}
105
+ };
106
+ const origWriteHead = res.writeHead.bind(res);
107
+ res.writeHead = (...args) => {
108
+ if (typeof args[0] === "number")
109
+ res.statusCode = args[0];
110
+ handleErrorResponse();
111
+ return origWriteHead(...args);
112
+ };
113
+ const origEnd = res.end.bind(res);
114
+ res.end = (...args) => {
115
+ handleErrorResponse();
116
+ return origEnd(...args);
117
+ };
118
+ next();
119
+ });
120
+ },
121
+ transform(code, id) {
122
+ const norm = id.replace(/\\/g, "/");
123
+ const isTarget = norm.includes("/@tanstack/start-server-core/src/request-response.ts") || norm.includes("/@tanstack/start-server-core/dist/esm/request-response.js");
124
+ if (!isTarget)
125
+ return null;
126
+ const needle = "handler(request, requestOpts)";
127
+ if (!code.includes(needle))
128
+ return null;
129
+ return code.replace(needle, `(globalThis.${HANDLER_RAN_KEY}?.(typeof h3Event === "undefined" ? undefined : h3Event), Promise.resolve(${needle}).catch((err) => { globalThis.${SSR_CAPTURE_KEY}?.(err); throw err; }))`);
130
+ }
131
+ };
132
+ }
133
+ function devServerFnErrorLogger() {
134
+ return {
135
+ name: "helloleo:dev-server-fn-error-logger",
136
+ apply: "serve",
137
+ enforce: "pre",
138
+ configureServer(server) {
139
+ globalThis[HMR_SEND_KEY] = (data) => {
140
+ server.ws.send({ type: "custom", event: "server-fn-error", data });
141
+ };
142
+ },
143
+ transform(code, id) {
144
+ const norm = id.replace(/\\/g, "/");
145
+ const isTarget = norm.includes("/@tanstack/start-server-core/src/server-functions-handler.ts") || norm.includes("/@tanstack/start-server-core/dist/esm/server-functions-handler.js");
146
+ if (!isTarget)
147
+ return null;
148
+ const needle = "const unwrapped = res.result || res.error";
149
+ if (!code.includes(needle))
150
+ return null;
151
+ return code.replace(needle, `${needle}
152
+ if (res?.error) {
153
+ const err = res.error
154
+ globalThis.${HMR_SEND_KEY}?.({
155
+ source: 'tanstack', type: 'server-fn-error',
156
+ method: request.method, url: request.url,
157
+ name: err?.name ?? 'Error', message: err?.message ?? String(err),
158
+ stack: typeof err?.stack === 'string' ? err.stack : undefined,
159
+ })
160
+ }`);
161
+ }
162
+ };
163
+ }
164
+
165
+ // src/sandbox.ts
166
+ function isSandboxEnvironment() {
167
+ return process.env.HELLOLEO_SANDBOX === "1" || !!process.env.DEV_SERVER__PROJECT_PATH;
168
+ }
169
+
170
+ // src/start.ts
171
+ var CF_SHIM = "@helloleo/runtime/cf-shim";
172
+ function defineConfig(options = {}) {
173
+ return async (env) => {
174
+ const { command, mode } = env;
175
+ const isDev = command === "serve";
176
+ const isSandbox = isSandboxEnvironment();
177
+ const target = command === "build" ? "workerd" : "sandbox";
178
+ const internalPlugins = [];
179
+ const tailwindcss = (await import("@tailwindcss/vite")).default;
180
+ internalPlugins.push(...tailwindcss());
181
+ const tsConfigPaths = (await import("vite-tsconfig-paths")).default;
182
+ internalPlugins.push(tsConfigPaths({ projects: ["./tsconfig.json"] }));
183
+ if (isDev && options.serverFnErrorLogger !== false)
184
+ internalPlugins.push(devServerFnErrorLogger());
185
+ if (isDev && options.ssrErrorLogger !== false)
186
+ internalPlugins.push(devSsrErrorLogger());
187
+ const { tanstackStart } = await import("@tanstack/react-start/plugin/vite");
188
+ internalPlugins.push(...tanstackStart({
189
+ importProtection: {
190
+ behavior: "error",
191
+ client: { files: ["**/server/**"], specifiers: ["server-only"] }
192
+ }
193
+ }));
194
+ const viteReact = (await import("@vitejs/plugin-react")).default;
195
+ internalPlugins.push(...viteReact());
196
+ if (!isDev) {
197
+ const { cloudflare } = await import("@cloudflare/vite-plugin");
198
+ internalPlugins.push(...cloudflare({ viteEnvironment: { name: "ssr" } }));
199
+ }
200
+ const envDefine = {};
201
+ for (const [key, value] of Object.entries(loadEnv(mode, process.cwd(), "VITE_"))) {
202
+ envDefine[`import.meta.env.${key}`] = JSON.stringify(value);
203
+ }
204
+ let config = {
205
+ define: { ...envDefine, __TARGET__: JSON.stringify(target) },
206
+ css: { transformer: "lightningcss" },
207
+ resolve: {
208
+ alias: {
209
+ "@": `${process.cwd()}/src`,
210
+ "#": `${process.cwd()}/src`,
211
+ ...isDev ? { "cloudflare:workers": CF_SHIM } : {}
212
+ },
213
+ dedupe: [
214
+ "react",
215
+ "react-dom",
216
+ "react/jsx-runtime",
217
+ "@tanstack/react-query",
218
+ "@tanstack/query-core"
219
+ ]
220
+ },
221
+ plugins: [...internalPlugins, ...options.plugins ?? []]
222
+ };
223
+ if (options.vite)
224
+ config = mergeConfig(config, options.vite);
225
+ if (isSandbox) {
226
+ config = mergeConfig(config, {
227
+ server: { host: "::", port: 8080, strictPort: true, allowedHosts: true }
228
+ });
229
+ } else {
230
+ config = mergeConfig(config, {
231
+ server: { host: "::", port: 8080, allowedHosts: true },
232
+ ...isDev ? {
233
+ server: {
234
+ watch: { awaitWriteFinish: { stabilityThreshold: 1000, pollInterval: 100 } }
235
+ }
236
+ } : {}
237
+ });
238
+ }
239
+ return config;
240
+ };
241
+ }
242
+ export {
243
+ defineConfig
244
+ };
package/package.json CHANGED
@@ -1,36 +1,55 @@
1
1
  {
2
2
  "name": "@helloleo/vite-config",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "authors": [
5
5
  "Herbie Vine <herbie@terros>"
6
6
  ],
7
- "license": "ISC",
7
+ "license": "UNLICENSED",
8
8
  "type": "module",
9
- "module": "src/index.ts",
9
+ "module": "./dist/start.js",
10
+ "types": "./dist/start.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
10
14
  "exports": {
11
- ".": "./src/index.ts"
12
- },
13
- "scripts": {
14
- "typecheck": "bunx tsc --noEmit"
15
- },
16
- "peerDependencies": {
17
- "@cloudflare/vite-plugin": "*",
18
- "@tailwindcss/vite": "*",
19
- "@tanstack/react-start": "*",
20
- "@vitejs/plugin-react": "*",
21
- "vite": "*",
22
- "vite-tsconfig-paths": "*"
15
+ ".": {
16
+ "types": "./dist/start.d.ts",
17
+ "default": "./dist/start.js"
23
18
  },
24
- "devDependencies": {
25
- "@cloudflare/vite-plugin": "^1.13.0",
26
- "@tailwindcss/vite": "^4.1.18",
27
- "@tanstack/react-start": "^1.132.0",
28
- "@types/bun": "latest",
29
- "@vitejs/plugin-react": "^5.0.4",
30
- "vite": "^7.0.0",
31
- "vite-tsconfig-paths": "^6.0.0"
32
- },
33
- "publishConfig": {
34
- "access": "public"
35
- }
19
+ "./start": {
20
+ "types": "./dist/start.d.ts",
21
+ "default": "./dist/start.js"
22
+ },
23
+ "./router": {
24
+ "types": "./dist/router.d.ts",
25
+ "default": "./dist/router.js"
26
+ }
27
+ },
28
+ "scripts": {
29
+ "build": "rm -rf dist && bun build ./src/start.ts ./src/router.ts --outdir ./dist --target node --format esm --packages external && tsc --project tsconfig.build.json",
30
+ "prepublishOnly": "bun run build",
31
+ "typecheck": "bunx tsc --noEmit"
32
+ },
33
+ "peerDependencies": {
34
+ "@cloudflare/vite-plugin": "*",
35
+ "@tailwindcss/vite": "*",
36
+ "@tanstack/react-start": "*",
37
+ "@tanstack/router-plugin": "*",
38
+ "@vitejs/plugin-react": "*",
39
+ "vite": "*",
40
+ "vite-tsconfig-paths": "*"
41
+ },
42
+ "devDependencies": {
43
+ "@cloudflare/vite-plugin": "^1.13.0",
44
+ "@tailwindcss/vite": "^4.1.18",
45
+ "@tanstack/react-start": "^1.132.0",
46
+ "@tanstack/router-plugin": "^1.132.0",
47
+ "@types/bun": "latest",
48
+ "@vitejs/plugin-react": "^5.0.4",
49
+ "vite": "^8.0.0",
50
+ "vite-tsconfig-paths": "^6.0.0"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
36
55
  }
@@ -1,177 +0,0 @@
1
- import type { Plugin } from "vite";
2
-
3
- // Two dev-only plugins that surface the REAL SSR / server-function error to the
4
- // browser (and the HelloLeo coder UI) over Vite's HMR websocket. Ported from
5
- // Lovable's vite-tanstack-config. Both monkeypatch TanStack Start internals via
6
- // `transform` — TanStack/h3 otherwise swallow in-handler throws into a generic
7
- // 500, so try/catch in app code never sees them.
8
-
9
- const SSR_CAPTURE_KEY = "__HELLOLEO_CAPTURE_SSR_ERROR__";
10
- const HANDLER_RAN_KEY = "__HELLOLEO_HANDLER_RAN__";
11
- const HMR_SEND_KEY = "__HELLOLEO_SERVER_FN_HMR_SEND__";
12
-
13
- const BUILD_ERROR_MESSAGE = /transform failed with \d+ error|failed to parse source/i;
14
-
15
- // Distinguish a Vite/esbuild transform (build) error from an app runtime error,
16
- // so the UI can label which layer failed.
17
- function isBuildError(error: unknown): boolean {
18
- if (typeof error === "string") return BUILD_ERROR_MESSAGE.test(error);
19
- if (!error || typeof error !== "object") return false;
20
- const e = error as Record<string, unknown>;
21
- if (
22
- typeof e.plugin === "string" &&
23
- (e.plugin.startsWith("vite:") || e.plugin.startsWith("esbuild:"))
24
- )
25
- return true;
26
- if (e.frame != null && e.loc != null) return true;
27
- if (
28
- Array.isArray(e.errors) &&
29
- e.errors.length > 0 &&
30
- e.errors.every((x) => x != null && typeof x === "object" && "text" in x && "location" in x)
31
- ) {
32
- return true;
33
- }
34
- const message = typeof e.message === "string" ? e.message : "";
35
- return BUILD_ERROR_MESSAGE.test(message);
36
- }
37
-
38
- export function devSsrErrorLogger(): Plugin {
39
- let lastCapture: { error: unknown; at: number } | undefined;
40
- const CAPTURE_TTL_MS = 5_000;
41
- const handlerRanFlag = Symbol("helloleoHandlerRan");
42
- const capture = (error: unknown) => {
43
- lastCapture = { error, at: Date.now() };
44
- };
45
- const consumeCapture = () => {
46
- if (!lastCapture) return undefined;
47
- if (Date.now() - lastCapture.at > CAPTURE_TTL_MS) {
48
- lastCapture = undefined;
49
- return undefined;
50
- }
51
- const { error } = lastCapture;
52
- lastCapture = undefined;
53
- return error;
54
- };
55
-
56
- return {
57
- name: "helloleo:dev-ssr-error-logger",
58
- apply: "serve",
59
- configureServer(server) {
60
- const g = globalThis as Record<string, unknown>;
61
- g[SSR_CAPTURE_KEY] = capture;
62
- g[HANDLER_RAN_KEY] = (event: { node?: { res?: Record<symbol, unknown> } }) => {
63
- const res = event?.node?.res;
64
- if (res) res[handlerRanFlag] = true;
65
- };
66
- if (typeof g.addEventListener === "function") {
67
- const add = g.addEventListener as (t: string, cb: (e: unknown) => void) => void;
68
- add("error", (e: unknown) => capture((e as { error?: unknown }).error ?? e));
69
- add("unhandledrejection", (e: unknown) => capture((e as { reason?: unknown }).reason));
70
- }
71
- const onUnhandledRejection = (reason: unknown) => capture(reason);
72
- process.on("unhandledRejection", onUnhandledRejection);
73
- server.httpServer?.once("close", () => {
74
- process.off("unhandledRejection", onUnhandledRejection);
75
- });
76
-
77
- server.middlewares.use((req, res, next) => {
78
- let handled = false;
79
- const handleErrorResponse = () => {
80
- if (handled || res.statusCode < 500) return;
81
- handled = true;
82
- const captured = consumeCapture();
83
- const ranHandler = Boolean((res as unknown as Record<symbol, unknown>)[handlerRanFlag]);
84
- let source: string;
85
- if (!ranHandler) source = "vite";
86
- else if (isBuildError(captured)) source = "build";
87
- else source = "app";
88
- if (!res.headersSent) {
89
- try {
90
- res.setHeader("x-helloleo-error-source", source);
91
- } catch {}
92
- }
93
- const where = [req.method, req.url].filter(Boolean).join(" ") || "the request";
94
- let data: { name?: string; message: string; stack?: string };
95
- if (captured instanceof Error) {
96
- data = { name: captured.name, message: captured.message, stack: captured.stack };
97
- } else if (typeof captured === "string" && captured.length > 0) {
98
- data = { name: "Error", message: captured };
99
- } else if (ranHandler) {
100
- data = {
101
- message: `The app returned ${res.statusCode} while handling ${where}. Handled by a route/error boundary, so no stack was captured — check the failing loader/route code.`,
102
- };
103
- } else {
104
- data = {
105
- message: `Dev server returned ${res.statusCode} for ${where} before the app handler ran — usually a Vite build/transform error. Check the dev server output.`,
106
- };
107
- }
108
- try {
109
- server.ws.send({ type: "custom", event: "server-ssr-error", data });
110
- } catch {}
111
- };
112
- const origWriteHead = res.writeHead.bind(res);
113
- // biome-ignore lint/suspicious/noExplicitAny: passthrough patch
114
- res.writeHead = (...args: any[]) => {
115
- if (typeof args[0] === "number") res.statusCode = args[0];
116
- handleErrorResponse();
117
- return origWriteHead(...(args as Parameters<typeof origWriteHead>));
118
- };
119
- const origEnd = res.end.bind(res);
120
- // biome-ignore lint/suspicious/noExplicitAny: passthrough patch
121
- res.end = (...args: any[]) => {
122
- handleErrorResponse();
123
- return origEnd(...(args as Parameters<typeof origEnd>));
124
- };
125
- next();
126
- });
127
- },
128
- transform(code, id) {
129
- const norm = id.replace(/\\/g, "/");
130
- const isTarget =
131
- norm.includes("/@tanstack/start-server-core/src/request-response.ts") ||
132
- norm.includes("/@tanstack/start-server-core/dist/esm/request-response.js");
133
- if (!isTarget) return null;
134
- const needle = "handler(request, requestOpts)";
135
- if (!code.includes(needle)) return null;
136
- return code.replace(
137
- needle,
138
- `(globalThis.${HANDLER_RAN_KEY}?.(typeof h3Event === "undefined" ? undefined : h3Event), Promise.resolve(${needle}).catch((err) => { globalThis.${SSR_CAPTURE_KEY}?.(err); throw err; }))`,
139
- );
140
- },
141
- };
142
- }
143
-
144
- export function devServerFnErrorLogger(): Plugin {
145
- return {
146
- name: "helloleo:dev-server-fn-error-logger",
147
- apply: "serve",
148
- enforce: "pre",
149
- configureServer(server) {
150
- (globalThis as Record<string, unknown>)[HMR_SEND_KEY] = (data: unknown) => {
151
- server.ws.send({ type: "custom", event: "server-fn-error", data });
152
- };
153
- },
154
- transform(code, id) {
155
- const norm = id.replace(/\\/g, "/");
156
- const isTarget =
157
- norm.includes("/@tanstack/start-server-core/src/server-functions-handler.ts") ||
158
- norm.includes("/@tanstack/start-server-core/dist/esm/server-functions-handler.js");
159
- if (!isTarget) return null;
160
- const needle = "const unwrapped = res.result || res.error";
161
- if (!code.includes(needle)) return null;
162
- return code.replace(
163
- needle,
164
- `${needle}
165
- if (res?.error) {
166
- const err = res.error
167
- globalThis.${HMR_SEND_KEY}?.({
168
- source: 'tanstack', type: 'server-fn-error',
169
- method: request.method, url: request.url,
170
- name: err?.name ?? 'Error', message: err?.message ?? String(err),
171
- stack: typeof err?.stack === 'string' ? err.stack : undefined,
172
- })
173
- }`,
174
- );
175
- },
176
- };
177
- }
package/src/index.ts DELETED
@@ -1,139 +0,0 @@
1
- // @helloleo/vite-config — one Vite config for every HelloLeo TanStack Start app.
2
- //
3
- // import { defineConfig } from "@helloleo/vite-config";
4
- // export default defineConfig();
5
- //
6
- // Hides all build/dev/sandbox plumbing so generated app code never edits (or
7
- // breaks) the Vite config, and infra upgrades ship centrally. Mirrors
8
- // @lovable.dev/vite-tanstack-config; the HelloLeo-specific part is the
9
- // cloudflare:workers alias that pairs with @helloleo/runtime.
10
- import { loadEnv, mergeConfig, type Plugin, type UserConfig } from "vite";
11
- import { devServerFnErrorLogger, devSsrErrorLogger } from "./dev-error-loggers.ts";
12
- import { isSandboxEnvironment } from "./sandbox.ts";
13
-
14
- export interface HelloLeoConfigOptions {
15
- // Extra Vite config merged last (escape hatch).
16
- vite?: UserConfig;
17
- // Extra plugins appended after the internal ones.
18
- plugins?: Plugin[];
19
- // Opt out of the dev error → HMR websocket bridges (default: on in dev).
20
- ssrErrorLogger?: boolean;
21
- serverFnErrorLogger?: boolean;
22
- }
23
-
24
- // Resolve the cf-shim specifier the dev alias points at. Kept as a bare module
25
- // specifier so it resolves from the app's node_modules (@helloleo/runtime is a
26
- // dep of the app, not of this config package).
27
- const CF_SHIM = "@helloleo/runtime/cf-shim";
28
-
29
- export function defineConfig(options: HelloLeoConfigOptions = {}) {
30
- return async (env: { command: string; mode: string }): Promise<UserConfig> => {
31
- const { command, mode } = env;
32
- const isDev = command === "serve";
33
- const isSandbox = isSandboxEnvironment();
34
- // __TARGET__ reflects the ACTIVE binding backend, derived from vite's
35
- // command — not the sandbox env var. `serve` always aliases
36
- // cloudflare:workers→cf-shim, so the live backend is the shim ("sandbox");
37
- // `build` ships native workerd bindings ("workerd"). The HELLOLEO_SANDBOX
38
- // var only gates sandbox hardening (port/host), never the target label.
39
- // (Under `wrangler dev` there's no vite, so __TARGET__ is undefined and app
40
- // code falls back to "unknown" — keep the typeof guard at call sites.)
41
- const target = command === "build" ? "workerd" : "sandbox";
42
-
43
- const internalPlugins: Plugin[] = [];
44
-
45
- const tailwindcss = (await import("@tailwindcss/vite")).default;
46
- internalPlugins.push(...(tailwindcss() as Plugin[]));
47
-
48
- const tsConfigPaths = (await import("vite-tsconfig-paths")).default;
49
- internalPlugins.push(tsConfigPaths({ projects: ["./tsconfig.json"] }));
50
-
51
- if (isDev && options.serverFnErrorLogger !== false)
52
- internalPlugins.push(devServerFnErrorLogger());
53
- if (isDev && options.ssrErrorLogger !== false) internalPlugins.push(devSsrErrorLogger());
54
-
55
- const { tanstackStart } = await import("@tanstack/react-start/plugin/vite");
56
- internalPlugins.push(
57
- ...(tanstackStart({
58
- // Build error if server-only code reaches the client bundle. Belt to the
59
- // .server.ts naming suspenders.
60
- importProtection: {
61
- behavior: "error",
62
- client: { files: ["**/server/**"], specifiers: ["server-only"] },
63
- },
64
- }) as Plugin[]),
65
- );
66
-
67
- const viteReact = (await import("@vitejs/plugin-react")).default;
68
- internalPlugins.push(...(viteReact() as Plugin[]));
69
-
70
- // PROD only: @cloudflare/vite-plugin builds the workerd Worker and binds the
71
- // real env (D1/KV/R2 from wrangler.jsonc) — `cloudflare:workers` resolves
72
- // natively. Skipped in dev: workerd can't nest in the sandbox preview VM, so
73
- // dev runs plain Vite SSR with the cf-shim alias instead (below). This is the
74
- // single dev/prod seam.
75
- if (!isDev) {
76
- const { cloudflare } = await import("@cloudflare/vite-plugin");
77
- internalPlugins.push(...(cloudflare({ viteEnvironment: { name: "ssr" } }) as Plugin[]));
78
- }
79
-
80
- // VITE_* env → import.meta.env, mirrored explicitly (matches build).
81
- const envDefine: Record<string, string> = {};
82
- for (const [key, value] of Object.entries(loadEnv(mode, process.cwd(), "VITE_"))) {
83
- envDefine[`import.meta.env.${key}`] = JSON.stringify(value);
84
- }
85
-
86
- let config: UserConfig = {
87
- // __TARGET__ literal lets app/runtime code dead-code-eliminate the wrong
88
- // branch (e.g. presign-only-S3 paths, prerender guards).
89
- define: { ...envDefine, __TARGET__: JSON.stringify(target) },
90
- // Lightning CSS in dev too — Vite uses PostCSS in dev / Lightning at build,
91
- // so a build-only CSS transform can break the static output while the dev
92
- // preview looks fine. Running Lightning in both keeps the preview honest
93
- // (matters for the no-code Design panel round-trip).
94
- css: { transformer: "lightningcss" },
95
- resolve: {
96
- alias: {
97
- "@": `${process.cwd()}/src`,
98
- "#": `${process.cwd()}/src`,
99
- // DEV (always): workerd can't run here, so back the bindings with the
100
- // local shim. PROD: @cloudflare/vite-plugin provides the real module,
101
- // so no alias.
102
- ...(isDev ? { "cloudflare:workers": CF_SHIM } : {}),
103
- },
104
- dedupe: [
105
- "react",
106
- "react-dom",
107
- "react/jsx-runtime",
108
- "@tanstack/react-query",
109
- "@tanstack/query-core",
110
- ],
111
- },
112
- plugins: [...internalPlugins, ...(options.plugins ?? [])],
113
- };
114
-
115
- if (options.vite) config = mergeConfig(config, options.vite);
116
-
117
- if (isSandbox) {
118
- // The CodeSandbox preview proxy requires a fixed host/port contract.
119
- config = mergeConfig(config, {
120
- server: { host: "::", port: 8080, strictPort: true, allowedHosts: true },
121
- });
122
- } else {
123
- // Local dev: debounce file watching — agents write files in bursts;
124
- // without this the dev server reloads on partial writes.
125
- config = mergeConfig(config, {
126
- server: { host: "::", port: 8080, allowedHosts: true },
127
- ...(isDev
128
- ? {
129
- server: {
130
- watch: { awaitWriteFinish: { stabilityThreshold: 1000, pollInterval: 100 } },
131
- },
132
- }
133
- : {}),
134
- });
135
- }
136
-
137
- return config;
138
- };
139
- }
package/src/sandbox.ts DELETED
@@ -1,9 +0,0 @@
1
- // Sandbox detection — the seam that drives __TARGET__ and the cloudflare:workers
2
- // alias. The HelloLeo dev harness (CodeSandbox launcher) sets HELLOLEO_SANDBOX=1
3
- // when it boots the dev server. DEV_SERVER__PROJECT_PATH is the secondary signal
4
- // kept compatible with the Lovable-style launcher contract.
5
- export function isSandboxEnvironment(): boolean {
6
- return process.env.HELLOLEO_SANDBOX === "1" || !!process.env.DEV_SERVER__PROJECT_PATH;
7
- }
8
-
9
- export type Target = "workerd" | "sandbox";
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "types": ["bun"]
5
- },
6
- "include": ["src/**/*.ts"]
7
- }