Use remix vite:dev in development

This commit is contained in:
Jason Laster 2025-02-27 13:15:58 -08:00
parent 1809aa89d7
commit 18f1983097
10 changed files with 272 additions and 19 deletions

2
.gitignore vendored
View File

@ -39,4 +39,4 @@ modelfiles
site
# commit file ignore
app/commit.json
app/commit.json

View File

@ -1,4 +1,9 @@
import * as Sentry from '@sentry/remix';
import { sentryHandleError } from '~/lib/sentry';
/**
* Using our conditional Sentry implementation instead of direct import
* This avoids loading Sentry in development environments
*/
import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare';
import { RemixServer } from '@remix-run/react';
import { isbot } from 'isbot';
@ -7,7 +12,7 @@ import { renderHeadToString } from 'remix-island';
import { Head } from './root';
import { themeStore } from '~/lib/stores/theme';
export const handleError = Sentry.sentryHandleError;
export const handleError = sentryHandleError;
export default async function handleRequest(
request: Request,
@ -26,10 +31,11 @@ export default async function handleRequest(
},
});
// @ts-ignore - Fix for incompatible EntryContext types between different remix versions
const head = renderHeadToString({ request, remixContext, Head });
const body = new ReadableStream({
start(controller) {
const head = renderHeadToString({ request, remixContext, Head });
controller.enqueue(
new Uint8Array(
new TextEncoder().encode(

View File

@ -5,7 +5,7 @@ import type { ContentBlockParam, MessageParam } from '@anthropic-ai/sdk/resource
import type { FileMap } from './stream-text';
import { StreamingMessageParser } from '~/lib/runtime/message-parser';
import { extractRelativePath } from '~/utils/diff';
import { wrapWithSpan, getCurrentSpan } from '~/lib/.server/otel';
import { wrapWithSpan, getCurrentSpan } from '~/lib/.server/otel-wrapper';
const Model = 'claude-3-7-sonnet-20250219';
const MaxMessageTokens = 8192;

View File

@ -0,0 +1,142 @@
/**
* Conditional OpenTelemetry implementation
* In development: Uses mock implementations that do nothing
* In production: Uses the real OpenTelemetry implementation
*/
import type { AppLoadContext } from '@remix-run/cloudflare';
// Function to check if we're in development environment
const isDevelopment = (): boolean => process.env.NODE_ENV === 'development';
// Types to match the original implementation
type Attributes = Record<string, any>;
type SpanOptions = {
name: string;
attrs?: Attributes;
};
// Mock implementations for development
const mockImplementations = {
ensureOpenTelemetryInitialized: (_context: AppLoadContext) => {
console.log('[DEV MODE - OpenTelemetry not loaded]: Skipping initialization');
},
wrapWithSpan: <Args extends any[], T>(
opts: SpanOptions,
fn: (...args: Args) => Promise<T>
): ((...args: Args) => Promise<T>) => {
// In development, just pass through the function without tracing
return fn;
},
getCurrentSpan: () => {
return null;
}
};
// Using a let variable so we can cache the imports in production
let otelModule: any = null;
// Helper to load the module once
const getOtelModule = async () => {
if (!otelModule && !isDevelopment()) {
try {
otelModule = await import('./otel');
} catch (e) {
console.error('Error loading OpenTelemetry:', e);
// Return null to indicate failure
return null;
}
}
return otelModule;
};
/**
* Ensure OpenTelemetry is initialized
* In development: Does nothing
* In production: Initializes OpenTelemetry
*/
export function ensureOpenTelemetryInitialized(context: AppLoadContext): void {
if (isDevelopment()) {
// Use mock in development
mockImplementations.ensureOpenTelemetryInitialized(context);
return;
}
// In production, initialize (this will happen asynchronously)
if (otelModule) {
// If module is already loaded, use it directly
otelModule.ensureOpenTelemetryInitialized(context);
} else {
// Otherwise trigger the async load and initialize when ready
getOtelModule().then(module => {
if (module) {
module.ensureOpenTelemetryInitialized(context);
}
}).catch(e => {
console.error('Failed to initialize OpenTelemetry:', e);
});
}
}
/**
* Wrap a function with a span for tracing
* In development: Just returns the original function
* In production: Wraps the function with a span
*/
export function wrapWithSpan<Args extends any[], T>(
opts: SpanOptions,
fn: (...args: Args) => Promise<T>
): ((...args: Args) => Promise<T>) {
if (isDevelopment()) {
// In development, just pass through without tracing
return fn;
}
// In production, create a wrapper function
return (...args: Args) => {
// If module is already loaded, use it directly
if (otelModule) {
return otelModule.wrapWithSpan(opts, fn)(...args);
}
// Otherwise trigger the async load for future calls
getOtelModule().then(() => {
// Module will be available for future calls
}).catch(e => {
console.error('Failed to load OpenTelemetry module:', e);
});
// For the current call, just use the function directly
return fn(...args);
};
}
/**
* Get the current span
* In development: Returns null
* In production: Returns the current span from OpenTelemetry
*/
export function getCurrentSpan(): any {
if (isDevelopment()) {
// In development, return null
return null;
}
// If module is already loaded, use it directly
if (otelModule) {
return otelModule.getCurrentSpan();
}
// Otherwise trigger the async load for future calls
getOtelModule().then(() => {
// Module will be available for future calls
}).catch(e => {
console.error('Failed to load OpenTelemetry module:', e);
});
// For the current call, return null
return null;
}

39
app/lib/sentry.ts Normal file
View File

@ -0,0 +1,39 @@
/**
* Conditional Sentry implementation
* In development: Uses a mock implementation that logs to console
* In production: Uses the real Sentry implementation
*/
// Function to check if we're in development environment
const isDevelopment = (): boolean => process.env.NODE_ENV === 'development';
/**
* Error handler function - wraps Sentry's error handling
* In development, it just logs the error and returns it
* In production, it sends the error to Sentry
*/
export function sentryHandleError(error: Error): Error {
if (isDevelopment()) {
// In development, just log the error
console.error('[DEV MODE - Sentry not loaded]:', error);
return error;
}
try {
/**
* In production, dynamically import and use Sentry
* This code only executes in production, so the import will never run in dev
*/
import('@sentry/remix')
.then((sentry) => {
sentry.captureException(error);
})
.catch((e) => {
console.error('Failed to load Sentry:', e);
});
} catch (e) {
console.error('Error while capturing exception:', e);
}
return error;
}

View File

@ -3,7 +3,7 @@ import { ChatStreamController } from '~/utils/chatStreamController';
import { assert } from '~/lib/replay/ReplayProtocolClient';
import { getStreamTextArguments, type FileMap, type Messages } from '~/lib/.server/llm/stream-text';
import { chatAnthropic, type AnthropicApiKey } from '~/lib/.server/llm/chat-anthropic';
import { ensureOpenTelemetryInitialized } from '~/lib/.server/otel';
import { ensureOpenTelemetryInitialized } from '~/lib/.server/otel-wrapper';
export async function action(args: ActionFunctionArgs) {
return chatAction(args);

18
app/routes/api.health.ts Normal file
View File

@ -0,0 +1,18 @@
import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
export const loader = async ({ request: _request }: LoaderFunctionArgs) => {
// Return a simple 200 OK response with some basic health information
return new Response(
JSON.stringify({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
}),
{
status: 200,
headers: {
'Content-Type': 'application/json',
},
},
);
};

View File

@ -156,4 +156,4 @@
"@opentelemetry/context-async-hooks@1.30.1": "patches/@opentelemetry__context-async-hooks@1.30.1.patch"
}
}
}
}

View File

@ -165,7 +165,7 @@ importers:
version: 8.55.0
'@sentry/remix':
specifier: ^8
version: 8.55.0(@opentelemetry/context-async-hooks@1.30.1(patch_hash=k7tq4qcxof3n2buu2yqtrqhjhu)(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@remix-run/node@2.15.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(react@18.3.1)
version: 8.55.0(@opentelemetry/context-async-hooks@1.30.1(patch_hash=k7tq4qcxof3n2buu2yqtrqhjhu)(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@remix-run/node@2.16.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(react@18.3.1)
'@sentry/vite-plugin':
specifier: ^3.1.2
version: 3.2.0
@ -267,10 +267,10 @@ importers:
version: 4.0.0
remix-island:
specifier: ^0.2.0
version: 0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/server-runtime@2.15.0(typescript@5.8.0-beta))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/server-runtime@2.16.0(typescript@5.8.0-beta))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
remix-utils:
specifier: ^7.7.0
version: 7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.8.0-beta))(@remix-run/node@2.15.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/router@1.21.0)(react@18.3.1)(zod@3.23.8)
version: 7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.8.0-beta))(@remix-run/node@2.16.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/router@1.23.0)(react@18.3.1)(zod@3.23.8)
shiki:
specifier: ^1.24.0
version: 1.24.0
@ -2415,6 +2415,15 @@ packages:
typescript:
optional: true
'@remix-run/node@2.16.0':
resolution: {integrity: sha512-9yYBYCHYO1+bIScGAtOy5/r4BoTS8E5lpQmjWP99UxSCSiKHPEO76V9Z8mmmarTNis/FPN+sUwfmbQWNHLA2vw==}
engines: {node: '>=18.0.0'}
peerDependencies:
typescript: ^5.1.0
peerDependenciesMeta:
typescript:
optional: true
'@remix-run/react@2.15.0':
resolution: {integrity: sha512-puqDbi9N/WfaUhzDnw2pACXtCB7ukrtFJ9ILwpEuhlaTBpjefifJ89igokW+tt1ePphIFMivAm/YspcbZdCQsA==}
engines: {node: '>=18.0.0'}
@ -2430,6 +2439,10 @@ packages:
resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==}
engines: {node: '>=14.0.0'}
'@remix-run/router@1.23.0':
resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
engines: {node: '>=14.0.0'}
'@remix-run/server-runtime@2.15.0':
resolution: {integrity: sha512-FuM8vAg1sPskf4wn0ivbuj/7s9Qdh2wnKu+sVXqYz0a95gH5b73TuMzk6n3NMSkFVKKc6+UmlG1WLYre7L2LTg==}
engines: {node: '>=18.0.0'}
@ -2439,6 +2452,15 @@ packages:
typescript:
optional: true
'@remix-run/server-runtime@2.16.0':
resolution: {integrity: sha512-gbuc4slxPi+pT47MrUYprX/wCuDlYL6H3LHZSvimWO1kDCBt8oefHzdHDPjLi4B1xzqXZomswTbuJzpZ7xRRTg==}
engines: {node: '>=18.0.0'}
peerDependencies:
typescript: ^5.1.0
peerDependenciesMeta:
typescript:
optional: true
'@remix-run/web-blob@3.1.0':
resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==}
@ -9274,6 +9296,18 @@ snapshots:
optionalDependencies:
typescript: 5.8.0-beta
'@remix-run/node@2.16.0(typescript@5.8.0-beta)':
dependencies:
'@remix-run/server-runtime': 2.16.0(typescript@5.8.0-beta)
'@remix-run/web-fetch': 4.4.2
'@web3-storage/multipart-parser': 1.0.0
cookie-signature: 1.2.2
source-map-support: 0.5.21
stream-slice: 0.1.2
undici: 6.21.0
optionalDependencies:
typescript: 5.8.0-beta
'@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta)':
dependencies:
'@remix-run/router': 1.21.0
@ -9288,6 +9322,8 @@ snapshots:
'@remix-run/router@1.21.0': {}
'@remix-run/router@1.23.0': {}
'@remix-run/server-runtime@2.15.0(typescript@5.8.0-beta)':
dependencies:
'@remix-run/router': 1.21.0
@ -9300,6 +9336,18 @@ snapshots:
optionalDependencies:
typescript: 5.8.0-beta
'@remix-run/server-runtime@2.16.0(typescript@5.8.0-beta)':
dependencies:
'@remix-run/router': 1.23.0
'@types/cookie': 0.6.0
'@web3-storage/multipart-parser': 1.0.0
cookie: 0.6.0
set-cookie-parser: 2.7.1
source-map: 0.7.4
turbo-stream: 2.4.0
optionalDependencies:
typescript: 5.8.0-beta
'@remix-run/web-blob@3.1.0':
dependencies:
'@remix-run/web-stream': 1.1.0
@ -9585,10 +9633,10 @@ snapshots:
hoist-non-react-statics: 3.3.2
react: 18.3.1
'@sentry/remix@8.55.0(@opentelemetry/context-async-hooks@1.30.1(patch_hash=k7tq4qcxof3n2buu2yqtrqhjhu)(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@remix-run/node@2.15.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(react@18.3.1)':
'@sentry/remix@8.55.0(@opentelemetry/context-async-hooks@1.30.1(patch_hash=k7tq4qcxof3n2buu2yqtrqhjhu)(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@remix-run/node@2.16.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(react@18.3.1)':
dependencies:
'@opentelemetry/api': 1.9.0
'@remix-run/node': 2.15.0(typescript@5.8.0-beta)
'@remix-run/node': 2.16.0(typescript@5.8.0-beta)
'@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta)
'@remix-run/router': 1.21.0
'@sentry/cli': 2.42.1
@ -13625,21 +13673,21 @@ snapshots:
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
remix-island@0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/server-runtime@2.15.0(typescript@5.8.0-beta))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
remix-island@0.2.0(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/server-runtime@2.16.0(typescript@5.8.0-beta))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta)
'@remix-run/server-runtime': 2.15.0(typescript@5.8.0-beta)
'@remix-run/server-runtime': 2.16.0(typescript@5.8.0-beta)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
remix-utils@7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.8.0-beta))(@remix-run/node@2.15.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/router@1.21.0)(react@18.3.1)(zod@3.23.8):
remix-utils@7.7.0(@remix-run/cloudflare@2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.8.0-beta))(@remix-run/node@2.16.0(typescript@5.8.0-beta))(@remix-run/react@2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta))(@remix-run/router@1.23.0)(react@18.3.1)(zod@3.23.8):
dependencies:
type-fest: 4.30.0
optionalDependencies:
'@remix-run/cloudflare': 2.15.0(@cloudflare/workers-types@4.20241127.0)(typescript@5.8.0-beta)
'@remix-run/node': 2.15.0(typescript@5.8.0-beta)
'@remix-run/node': 2.16.0(typescript@5.8.0-beta)
'@remix-run/react': 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.0-beta)
'@remix-run/router': 1.21.0
'@remix-run/router': 1.23.0
react: 18.3.1
zod: 3.23.8

View File

@ -16,7 +16,7 @@ let commitJson = {
console.log(`
B O L T . D I Y
NUT.NEW
Welcome
`);