@sticklightapp/tanstack-vite-config 0.0.0-stage → 0.0.5

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
@@ -1,3 +1,34 @@
1
- # Temporary Holding Version
1
+ # @sticklightapp/tanstack-vite-config
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Vite configuration for Sticklight TanStack Start apps.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { defineConfig } from '@sticklightapp/tanstack-vite-config';
9
+
10
+ export default defineConfig({
11
+ tanstackStart: {
12
+ prerender: {
13
+ enabled: true,
14
+ },
15
+ },
16
+ });
17
+ ```
18
+
19
+ ## Options
20
+
21
+ | Option | Description |
22
+ | --------------- | ----------------------------------------- |
23
+ | `vite` | Allowed `root`, `resolve`, `optimizeDeps`, `css`, `define`, and `ssr` options. |
24
+ | `plugins` | Extra Vite plugins. |
25
+ | `tanstackStart` | Options for `tanstackStart()`. |
26
+ | `react` | Options for `@vitejs/plugin-react`. |
27
+ | `nitro` | `compatibilityDate` and `wrangler`. |
28
+ | `tailwindCdn` | `themeCssPath` (default `src/theme.css`). |
29
+
30
+ Unsupported Vite keys are ignored with a structured warning. Host, port, strict-port, HMR overlay, and watch-ignore settings stay platform-owned.
31
+
32
+ ## Peer dependencies
33
+
34
+ `vite`, `@tanstack/react-start`.
@@ -0,0 +1,25 @@
1
+ import { tanstackStart } from '@tanstack/react-start/plugin/vite';
2
+ import viteReact from '@vitejs/plugin-react';
3
+ import { type ConfigEnv, type PluginOption, type UserConfig } from 'vite';
4
+ import { type NitroOptions } from './nitro.js';
5
+ import { type TailwindCdnOptions } from './plugins/tailwind-cdn.js';
6
+ import { type AllowedViteConfig } from './resolve-vite-config.js';
7
+ type TanstackStartOptions = NonNullable<Parameters<typeof tanstackStart>[0]>;
8
+ type ReactOptions = NonNullable<Parameters<typeof viteReact>[0]>;
9
+ export interface TanstackViteConfigOptions {
10
+ /** Allowed Vite options merged over platform defaults. Unsupported runtime keys are ignored with a warning. */
11
+ vite?: AllowedViteConfig;
12
+ /** Appended after the platform plugins. */
13
+ plugins?: PluginOption[];
14
+ /** Forwarded to `tanstackStart()`. Route scaffolding stays platform-owned. */
15
+ tanstackStart?: TanstackStartOptions;
16
+ /** Forwarded to `viteReact()`. */
17
+ react?: ReactOptions;
18
+ /** Applied to the build-only Nitro Cloudflare plugin. The preset and deploy config stay platform-owned. */
19
+ nitro?: NitroOptions;
20
+ /** Applied to the dev-only Tailwind browser build. */
21
+ tailwindCdn?: TailwindCdnOptions;
22
+ }
23
+ export declare function defineConfig(options?: TanstackViteConfigOptions): (env: ConfigEnv) => Promise<UserConfig>;
24
+ export {};
25
+ //# sourceMappingURL=define-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-config.d.ts","sourceRoot":"","sources":["../src/define-config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,SAAS,MAAM,sBAAsB,CAAC;AAG7C,OAAO,EAAgB,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AACxF,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAGtE,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAA4C,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG5G,KAAK,oBAAoB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7E,KAAK,YAAY,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEjE,MAAM,WAAW,yBAAyB;IACzC,+GAA+G;IAC/G,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,kCAAkC;IAClC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,2GAA2G;IAC3G,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,sDAAsD;IACtD,WAAW,CAAC,EAAE,kBAAkB,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,yBAA8B,IACrD,KAAK,SAAS,KAAG,OAAO,CAAC,UAAU,CAAC,CAmBlD"}
@@ -0,0 +1,91 @@
1
+ import { createViteLogger } from '@sticklightapp/sandbox-logger';
2
+ import tailwindcss from '@tailwindcss/vite';
3
+ import { tanstackStart } from '@tanstack/react-start/plugin/vite';
4
+ import viteReact from '@vitejs/plugin-react';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { createLogger } from 'vite';
8
+ import { buildOnlyNitroPlugins } from './nitro.js';
9
+ import { DEV_SCRIPTS_INJECTOR, injectDevToolsPlugin } from './plugins/dev-tools.js';
10
+ import { structuredLoggerPlugin } from './plugins/structured-logger.js';
11
+ import { tailwindCdnPlugin } from './plugins/tailwind-cdn.js';
12
+ import { resolveViteConfig, SANDBOX_WATCH_IGNORES } from './resolve-vite-config.js';
13
+ import { captureConsoleErrors, emit } from './sandbox-log.js';
14
+ export function defineConfig(options = {}) {
15
+ return async (env) => {
16
+ captureConsoleErrors();
17
+ const root = path.resolve(options.vite?.root ?? process.cwd());
18
+ const platformConfig = {
19
+ ...createPlatformConfig(root),
20
+ plugins: await createPlugins(env, options),
21
+ };
22
+ const config = resolveViteConfig(platformConfig, options.vite);
23
+ return {
24
+ ...config,
25
+ // createViteLogger overrides error() too, so RollupError fields (plugin, loc, frame)
26
+ // reach the log as real fields instead of being formatted into one string.
27
+ customLogger: createViteLogger(createLogger(), emit),
28
+ };
29
+ };
30
+ }
31
+ function createPlatformConfig(root) {
32
+ return {
33
+ server: {
34
+ host: '0.0.0.0', // Required for Daytona sandbox proxy access
35
+ port: 5173,
36
+ strictPort: true,
37
+ hmr: {
38
+ overlay: false,
39
+ },
40
+ warmup: {
41
+ // Pre-transform the platform-owned entries on startup so the first request is instant.
42
+ // Agent-owned route files are deliberately absent: they get moved into layouts and
43
+ // nested directories, and a stale path here makes Vite log a standing pre-transform error.
44
+ clientFiles: ['./src/routes/__root.tsx', './src/router.tsx'],
45
+ },
46
+ watch: {
47
+ ignored: SANDBOX_WATCH_IGNORES,
48
+ },
49
+ },
50
+ optimizeDeps: {
51
+ // Pre-bundle these on startup so the first page load doesn't trigger re-optimization
52
+ include: ['react', 'react-dom', '@supabase/supabase-js', 'lucide-react', '@tanstack/react-router', DEV_SCRIPTS_INJECTOR],
53
+ },
54
+ resolve: {
55
+ alias: {
56
+ '@': path.resolve(root, 'src'),
57
+ [DEV_SCRIPTS_INJECTOR]: fileURLToPath(import.meta.resolve(DEV_SCRIPTS_INJECTOR)),
58
+ },
59
+ dedupe: ['react', 'react-dom'],
60
+ },
61
+ };
62
+ }
63
+ async function createPlugins(env, options) {
64
+ const nitroPlugins = env.command === 'build' ? await buildOnlyNitroPlugins(options.nitro) : [];
65
+ return [
66
+ tailwindcss(),
67
+ tailwindCdnPlugin(options.tailwindCdn),
68
+ tanstackStart(resolveTanstackStartOptions(options.tanstackStart)),
69
+ ...nitroPlugins,
70
+ // viteReact must come AFTER tanstackStart
71
+ viteReact(options.react),
72
+ injectDevToolsPlugin(),
73
+ structuredLoggerPlugin(),
74
+ ...(options.plugins ?? []),
75
+ ];
76
+ }
77
+ function resolveTanstackStartOptions(overrides = {}) {
78
+ return {
79
+ ...overrides,
80
+ server: {
81
+ entry: 'server',
82
+ ...overrides.server,
83
+ },
84
+ router: {
85
+ ...overrides.router,
86
+ // Uploads are not atomic, so the generator can see a route file while it is still empty.
87
+ // Its default scaffold would replace the file mid-write; empty templates make it skip the file.
88
+ customScaffolding: { routeTemplate: '', lazyRouteTemplate: '' },
89
+ },
90
+ };
91
+ }
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { defineConfig, type TanstackViteConfigOptions } from './define-config.js';
2
+ export type { NitroOptions } from './nitro.js';
3
+ export type { TailwindCdnOptions } from './plugins/tailwind-cdn.js';
4
+ export type { AllowedViteConfig } from './resolve-vite-config.js';
5
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACpE,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC"}
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export { defineConfig } from './define-config.js';
package/nitro.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { nitro } from 'nitro/vite';
2
+ import type { PluginOption } from 'vite';
3
+ type NitroPluginOptions = NonNullable<Parameters<typeof nitro>[0]>;
4
+ type CloudflareOptions = NonNullable<NitroPluginOptions['cloudflare']>;
5
+ /** Kept narrow on purpose: Nitro v3 is pre-RC, and the deploy pipeline reads `.output/server/wrangler.json`. */
6
+ export interface NitroOptions {
7
+ compatibilityDate?: NitroPluginOptions['compatibilityDate'];
8
+ /** Shallow-merged into the generated `wrangler.json`. */
9
+ wrangler?: CloudflareOptions['wrangler'];
10
+ }
11
+ export declare function buildOnlyNitroPlugins(options?: NitroOptions): Promise<PluginOption[]>;
12
+ export {};
13
+ //# sourceMappingURL=nitro.d.ts.map
package/nitro.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nitro.d.ts","sourceRoot":"","sources":["../src/nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,KAAK,kBAAkB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,KAAK,iBAAiB,GAAG,WAAW,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAC;AAEvE,gHAAgH;AAChH,MAAM,WAAW,YAAY;IAC5B,iBAAiB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;IAC5D,yDAAyD;IACzD,QAAQ,CAAC,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;CACzC;AAUD,wBAAsB,qBAAqB,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAe/F"}
package/nitro.js ADDED
@@ -0,0 +1,21 @@
1
+ const DEFAULT_COMPATIBILITY_DATE = '2026-08-04';
2
+ const DEFAULT_WRANGLER = {
3
+ name: 'tanstack-ssr-app',
4
+ };
5
+ // Deploy reads `.output/server/wrangler.json`, which Nitro writes. Loaded only for builds: the dev
6
+ // server runs on plain Node.
7
+ export async function buildOnlyNitroPlugins(options = {}) {
8
+ const { nitro } = await import('nitro/vite');
9
+ return nitro({
10
+ preset: 'cloudflare-module',
11
+ compatibilityDate: options.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE,
12
+ cloudflare: {
13
+ deployConfig: true,
14
+ nodeCompat: true,
15
+ wrangler: {
16
+ ...DEFAULT_WRANGLER,
17
+ ...options.wrangler,
18
+ },
19
+ },
20
+ });
21
+ }
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@sticklightapp/tanstack-vite-config",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.5",
4
+ "description": "Vite configuration for Sticklight TanStack Start apps",
5
+ "keywords": [
6
+ "vite",
7
+ "vite-config",
8
+ "tanstack",
9
+ "tanstack-start"
10
+ ],
11
+ "type": "module",
12
+ "main": "./index.js",
13
+ "types": "./index.d.ts",
14
+ "exports": {
15
+ "./package.json": "./package.json",
16
+ ".": {
17
+ "types": "./index.d.ts",
18
+ "import": "./index.js",
19
+ "default": "./index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "**/*.js",
24
+ "**/*.d.ts",
25
+ "**/*.d.ts.map",
26
+ "README.md"
27
+ ],
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "provenance": false
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/elementor/elementor-vibe.git",
35
+ "directory": "packages/tanstack-vite-config"
36
+ },
37
+ "dependencies": {
38
+ "@elementor/sandbox-dev-scripts-injector": "0.0.24",
39
+ "@sticklightapp/sandbox-logger": "0.0.28",
40
+ "@tailwindcss/vite": "4.3.3",
41
+ "@vitejs/plugin-react": "6.1.2",
42
+ "nitro": "3.0.260903-beta",
43
+ "tslib": "^2.8.1"
44
+ },
45
+ "peerDependencies": {
46
+ "@tanstack/react-start": "^1.169.3",
47
+ "vite": "^8.3.0"
48
+ }
6
49
  }
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare const DEV_SCRIPTS_INJECTOR = "@elementor/sandbox-dev-scripts-injector";
3
+ export declare function injectDevToolsPlugin(): Plugin;
4
+ //# sourceMappingURL=dev-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-tools.d.ts","sourceRoot":"","sources":["../../src/plugins/dev-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,eAAO,MAAM,oBAAoB,4CAA4C,CAAC;AAE9E,wBAAgB,oBAAoB,IAAI,MAAM,CAe7C"}
@@ -0,0 +1,17 @@
1
+ import { appendDynamicImport, isViteClient, warnIfViteClientMissing } from '../vite-client.js';
2
+ export const DEV_SCRIPTS_INJECTOR = '@elementor/sandbox-dev-scripts-injector';
3
+ export function injectDevToolsPlugin() {
4
+ return {
5
+ name: 'inject-dev-tools',
6
+ apply: 'serve',
7
+ configureServer(server) {
8
+ warnIfViteClientMissing(server.config.root);
9
+ },
10
+ transform(code, id) {
11
+ if (!isViteClient(this.environment, id)) {
12
+ return;
13
+ }
14
+ return appendDynamicImport(code, DEV_SCRIPTS_INJECTOR);
15
+ },
16
+ };
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function structuredLoggerPlugin(): Plugin;
3
+ //# sourceMappingURL=structured-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-logger.d.ts","sourceRoot":"","sources":["../../src/plugins/structured-logger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,MAAM,EAAE,MAAM,MAAM,CAAC;AAK/C,wBAAgB,sBAAsB,IAAI,MAAM,CAsB/C"}
@@ -0,0 +1,36 @@
1
+ import { buildRecord } from '@sticklightapp/sandbox-logger';
2
+ import { emit } from '../sandbox-log.js';
3
+ // Intercepts Vite's WebSocket to log HMR events into /tmp/dev.log.
4
+ // sandbox.service.ts reads these events to detect build success and errors.
5
+ export function structuredLoggerPlugin() {
6
+ return {
7
+ name: 'structured-logger',
8
+ configureServer(server) {
9
+ const originalSend = server.ws.send;
10
+ server.ws.send = ((...args) => {
11
+ emitHotPayload(args[0]);
12
+ return Reflect.apply(originalSend, server.ws, args);
13
+ });
14
+ },
15
+ buildEnd(error) {
16
+ if (error) {
17
+ emit({ ...buildRecord('error', error.message, { error }), event: 'build-error' });
18
+ return;
19
+ }
20
+ emit({ ts: Date.now(), level: 'info', source: 'devserver', event: 'build-success', message: 'build succeeded' });
21
+ },
22
+ };
23
+ }
24
+ function emitHotPayload(payload) {
25
+ if (typeof payload !== 'object' || payload === null || !('type' in payload)) {
26
+ return;
27
+ }
28
+ const hotPayload = payload;
29
+ if (hotPayload.type === 'update') {
30
+ hotPayload.updates.forEach((update) => emit({ ts: Date.now(), level: 'info', source: 'devserver', event: 'hmr-update', message: update.path, file: update.path }));
31
+ return;
32
+ }
33
+ if (hotPayload.type === 'full-reload') {
34
+ emit({ ts: Date.now(), level: 'info', source: 'devserver', event: 'page-reload', message: hotPayload.path ?? '*' });
35
+ }
36
+ }
@@ -0,0 +1,7 @@
1
+ import type { Plugin } from 'vite';
2
+ export interface TailwindCdnOptions {
3
+ /** Theme file passed to the Tailwind browser build, resolved from the app root. Defaults to `src/theme.css`. */
4
+ themeCssPath?: string;
5
+ }
6
+ export declare function tailwindCdnPlugin(options?: TailwindCdnOptions): Plugin;
7
+ //# sourceMappingURL=tailwind-cdn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tailwind-cdn.d.ts","sourceRoot":"","sources":["../../src/plugins/tailwind-cdn.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAQlD,MAAM,WAAW,kBAAkB;IAClC,gHAAgH;IAChH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAyC1E"}
@@ -0,0 +1,77 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { emit } from '../sandbox-log.js';
4
+ import { appendDynamicImport, isViteClient } from '../vite-client.js';
5
+ const TAILWIND_CDN_MODULE_ID = 'virtual:tailwind-cdn';
6
+ const RESOLVED_TAILWIND_CDN_MODULE_ID = `\0${TAILWIND_CDN_MODULE_ID}`;
7
+ const DEFAULT_THEME_CSS_PATH = 'src/theme.css';
8
+ // Keep this version aligned with the @tailwindcss/vite peer dependency.
9
+ const TAILWIND_CDN_SRC = 'https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3/dist/index.global.js';
10
+ const TAILWIND_CDN_INTEGRITY = 'sha384-2ql948lIdLcGEE0/qxNiudyTjgauA3RDJERu5xW75kFCvSl5a9odyQYCb6tEjnmB';
11
+ // Injects the Tailwind browser build in dev mode, with the app's theme as its config.
12
+ export function tailwindCdnPlugin(options = {}) {
13
+ const relativeThemeCssPath = options.themeCssPath ?? DEFAULT_THEME_CSS_PATH;
14
+ const resolveThemeCssPath = (root) => path.resolve(root, relativeThemeCssPath);
15
+ return {
16
+ name: 'tailwind-cdn',
17
+ apply: 'serve',
18
+ configureServer(server) {
19
+ watchThemeCss(server, resolveThemeCssPath(server.config.root));
20
+ },
21
+ resolveId(id) {
22
+ return id === TAILWIND_CDN_MODULE_ID ? RESOLVED_TAILWIND_CDN_MODULE_ID : undefined;
23
+ },
24
+ async load(id) {
25
+ if (id !== RESOLVED_TAILWIND_CDN_MODULE_ID) {
26
+ return;
27
+ }
28
+ const themeCssPath = resolveThemeCssPath(this.environment.config.root);
29
+ try {
30
+ const themeCss = await readFile(themeCssPath, 'utf-8');
31
+ emit({ ts: Date.now(), level: 'info', source: 'devserver', event: 'tailwind-theme-read', message: themeCssPath });
32
+ return renderTailwindCdnLoader(themeCss);
33
+ }
34
+ catch {
35
+ emit({ ts: Date.now(), level: 'warn', source: 'devserver', event: 'tailwind-theme-missing', message: themeCssPath });
36
+ return '';
37
+ }
38
+ },
39
+ transform(code, id) {
40
+ if (!isViteClient(this.environment, id)) {
41
+ return;
42
+ }
43
+ return appendDynamicImport(code, TAILWIND_CDN_MODULE_ID);
44
+ },
45
+ };
46
+ }
47
+ function watchThemeCss(server, themeCssPath) {
48
+ server.watcher.add(themeCssPath);
49
+ const invalidate = (filePath) => {
50
+ if (filePath !== themeCssPath) {
51
+ return;
52
+ }
53
+ const clientModuleGraph = server.environments.client.moduleGraph;
54
+ const tailwindCdnModule = clientModuleGraph.getModuleById(RESOLVED_TAILWIND_CDN_MODULE_ID);
55
+ if (tailwindCdnModule) {
56
+ clientModuleGraph.invalidateModule(tailwindCdnModule);
57
+ }
58
+ server.ws.send({ type: 'full-reload', path: '*' });
59
+ emit({ ts: Date.now(), level: 'info', source: 'devserver', event: 'tailwind-theme-invalidated', message: themeCssPath });
60
+ };
61
+ server.watcher.on('change', invalidate);
62
+ server.watcher.on('add', invalidate);
63
+ server.watcher.on('unlink', invalidate);
64
+ }
65
+ function renderTailwindCdnLoader(themeCss) {
66
+ return `const script = document.createElement('script');
67
+ script.src = ${JSON.stringify(TAILWIND_CDN_SRC)};
68
+ script.integrity = ${JSON.stringify(TAILWIND_CDN_INTEGRITY)};
69
+ script.crossOrigin = 'anonymous';
70
+
71
+ const theme = document.createElement('style');
72
+ theme.type = 'text/tailwindcss';
73
+ theme.textContent = ${JSON.stringify(themeCss)};
74
+
75
+ document.head.prepend(script, theme);
76
+ `;
77
+ }
@@ -0,0 +1,7 @@
1
+ import { type UserConfig } from 'vite';
2
+ declare const ALLOWED_VITE_KEYS: readonly ["root", "resolve", "optimizeDeps", "css", "define", "ssr"];
3
+ export type AllowedViteConfig = Pick<UserConfig, (typeof ALLOWED_VITE_KEYS)[number]>;
4
+ export declare const SANDBOX_WATCH_IGNORES: string[];
5
+ export declare function resolveViteConfig(platformConfig: UserConfig, overrides: AllowedViteConfig | undefined): UserConfig;
6
+ export {};
7
+ //# sourceMappingURL=resolve-vite-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-vite-config.d.ts","sourceRoot":"","sources":["../src/resolve-vite-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAGpD,QAAA,MAAM,iBAAiB,sEAAuE,CAAC;AAE/F,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAErF,eAAO,MAAM,qBAAqB,UAAsD,CAAC;AAEzF,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAElH"}
@@ -0,0 +1,23 @@
1
+ import { mergeConfig } from 'vite';
2
+ import { emit } from './sandbox-log.js';
3
+ const ALLOWED_VITE_KEYS = ['root', 'resolve', 'optimizeDeps', 'css', 'define', 'ssr'];
4
+ export const SANDBOX_WATCH_IGNORES = ['**/.sc/**', '**/vite-ignore/**', '**/.output/**'];
5
+ export function resolveViteConfig(platformConfig, overrides) {
6
+ return mergeConfig(platformConfig, pickAllowedViteConfig(overrides));
7
+ }
8
+ function pickAllowedViteConfig(overrides) {
9
+ const entries = Object.entries(overrides ?? {});
10
+ const allowedKeys = new Set(ALLOWED_VITE_KEYS);
11
+ for (const [key] of entries) {
12
+ if (!allowedKeys.has(key)) {
13
+ emit({
14
+ ts: Date.now(),
15
+ level: 'warn',
16
+ source: 'devserver',
17
+ event: 'vite-config-override-ignored',
18
+ message: `Ignored sandbox Vite override "vite.${key}"`,
19
+ });
20
+ }
21
+ }
22
+ return Object.fromEntries(entries.filter(([key]) => allowedKeys.has(key)));
23
+ }
@@ -0,0 +1,4 @@
1
+ import { type SandboxLogRecord } from '@sticklightapp/sandbox-logger';
2
+ export declare function emit(record: SandboxLogRecord): void;
3
+ export declare function captureConsoleErrors(): void;
4
+ //# sourceMappingURL=sandbox-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-log.d.ts","sourceRoot":"","sources":["../src/sandbox-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,KAAK,gBAAgB,EAAyB,MAAM,+BAA+B,CAAC;AAO5I,wBAAgB,IAAI,CAAC,MAAM,EAAE,gBAAgB,QAI5C;AAID,wBAAgB,oBAAoB,SAgBnC"}
package/sandbox-log.js ADDED
@@ -0,0 +1,36 @@
1
+ import { buildRecord, createSandboxLogger, writeRecord } from '@sticklightapp/sandbox-logger';
2
+ // Every record written here conforms to SandboxLogRecord (@sticklightapp/sandbox-logger). Consumers read
3
+ // the schema, never this package — see the sandbox-logger README before changing what is emitted.
4
+ let sink;
5
+ let isCapturingConsoleErrors = false;
6
+ export function emit(record) {
7
+ sink ??= createSandboxLogger();
8
+ writeRecord(sink, record);
9
+ }
10
+ // Fallback only: plugins and dependencies that bypass Vite's logger and write straight to the
11
+ // console. Vite's own errors take the customLogger path, where the fields are still intact.
12
+ export function captureConsoleErrors() {
13
+ if (isCapturingConsoleErrors) {
14
+ return;
15
+ }
16
+ isCapturingConsoleErrors = true;
17
+ console.error = (...args) => {
18
+ const error = args.find((arg) => arg instanceof Error);
19
+ const message = args
20
+ .filter((arg) => arg !== error)
21
+ .map(formatConsoleArg)
22
+ .join(' ');
23
+ emit(buildRecord('error', message || (error?.message ?? ''), error ? { error } : undefined));
24
+ };
25
+ }
26
+ function formatConsoleArg(arg) {
27
+ if (typeof arg !== 'object' || arg === null) {
28
+ return String(arg);
29
+ }
30
+ try {
31
+ return JSON.stringify(arg);
32
+ }
33
+ catch {
34
+ return String(arg);
35
+ }
36
+ }
@@ -0,0 +1,6 @@
1
+ export declare function isViteClient(environment: {
2
+ name: string;
3
+ } | undefined, id: string): boolean;
4
+ export declare function warnIfViteClientMissing(root: string): void;
5
+ export declare function appendDynamicImport(code: string, specifier: string): string;
6
+ //# sourceMappingURL=vite-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-client.d.ts","sourceRoot":"","sources":["../src/vite-client.ts"],"names":[],"mappings":"AAUA,wBAAgB,YAAY,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE3F;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,QAYnD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAI3E"}
package/vite-client.js ADDED
@@ -0,0 +1,29 @@
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+ import { emit } from './sandbox-log.js';
4
+ // TanStack Start renders the document itself and never calls transformIndexHtml. Every dev page loads
5
+ // Vite's HMR client, so dev-only browser modules are imported from it instead. The imports are dynamic
6
+ // so a failing module cannot break HMR. Failures are logged with console.warn, because the dev-scripts
7
+ // injector reports unhandled rejections to the agent as project errors.
8
+ const VITE_CLIENT_SPECIFIER = 'vite/dist/client/client.mjs';
9
+ export function isViteClient(environment, id) {
10
+ return environment?.name === 'client' && id.includes(`/${VITE_CLIENT_SPECIFIER}`);
11
+ }
12
+ export function warnIfViteClientMissing(root) {
13
+ try {
14
+ createRequire(path.join(root, 'package.json')).resolve(VITE_CLIENT_SPECIFIER);
15
+ }
16
+ catch {
17
+ emit({
18
+ ts: Date.now(),
19
+ level: 'warn',
20
+ source: 'devserver',
21
+ event: 'vite-client-missing',
22
+ message: `${VITE_CLIENT_SPECIFIER} not found, Tailwind CDN and dev tools will not be injected`,
23
+ });
24
+ }
25
+ }
26
+ export function appendDynamicImport(code, specifier) {
27
+ const failureMessage = `[sandbox] Failed to load ${specifier}`;
28
+ return `${code}\nimport(${JSON.stringify(specifier)}).catch((error) => console.warn(${JSON.stringify(failureMessage)}, error));\n`;
29
+ }