Compare commits
16 Commits
437d110e37
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f646ee43ce | ||
|
|
cfb60d04ce | ||
|
|
f24a9ddbdf | ||
|
|
28093ccd22 | ||
|
|
9d8c4055ca | ||
|
|
3f6050b227 | ||
|
|
5f925566c4 | ||
|
|
983b3025a5 | ||
|
|
49850d9253 | ||
|
|
1a55bd5866 | ||
|
|
f138b9c088 | ||
|
|
4e37f5a80c | ||
|
|
d34852c227 | ||
|
|
cb3c536c5d | ||
|
|
b32c4081ec | ||
|
|
583dfbda53 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -74,7 +74,7 @@ jobs:
|
||||
- name: Validate Docker production build
|
||||
run: |
|
||||
echo "🐳 Testing Docker production target..."
|
||||
docker build --target runtime . --no-cache --progress=plain
|
||||
docker build --target bolt-ai-production . --no-cache --progress=plain
|
||||
echo "✅ Production target builds successfully"
|
||||
|
||||
- name: Validate Docker development build
|
||||
|
||||
2
.github/workflows/docker.yaml
vendored
2
.github/workflows/docker.yaml
vendored
@@ -57,7 +57,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
target: runtime
|
||||
target: bolt-ai-production
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
122
Dockerfile
122
Dockerfile
@@ -1,92 +1,56 @@
|
||||
# ---- build stage ----
|
||||
FROM node:22-bookworm-slim AS build
|
||||
WORKDIR /app
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:22-bookworm-slim AS base
|
||||
|
||||
# CI-friendly env
|
||||
ENV HUSKY=0
|
||||
ENV CI=true
|
||||
|
||||
# Use pnpm
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.9 --activate
|
||||
|
||||
# Accept (optional) build-time public URL for Remix/Vite (Coolify can pass it)
|
||||
ARG VITE_PUBLIC_APP_URL
|
||||
ENV VITE_PUBLIC_APP_URL=${VITE_PUBLIC_APP_URL}
|
||||
|
||||
# Install deps efficiently
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
RUN pnpm fetch
|
||||
|
||||
# Copy source and build
|
||||
COPY . .
|
||||
# install with dev deps (needed to build)
|
||||
RUN pnpm install --offline --frozen-lockfile
|
||||
|
||||
# Build the Remix app (SSR + client)
|
||||
RUN NODE_OPTIONS=--max-old-space-size=4096 pnpm run build
|
||||
|
||||
# Keep only production deps for runtime
|
||||
RUN pnpm prune --prod --ignore-scripts
|
||||
|
||||
|
||||
# ---- runtime stage ----
|
||||
FROM node:22-bookworm-slim AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
ENV HOST=0.0.0.0
|
||||
|
||||
# Install curl so Coolify’s healthcheck works inside the image
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl \
|
||||
# Install system dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy only what we need to run
|
||||
COPY --from=build /app/build /app/build
|
||||
COPY --from=build /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/package.json /app/package.json
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 3000
|
||||
# Dependencies stage
|
||||
FROM base AS deps
|
||||
|
||||
# Healthcheck for Coolify
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=5 \
|
||||
CMD curl -fsS http://localhost:3000/ || exit 1
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Start the Remix server
|
||||
CMD ["node", "build/server/index.js"]
|
||||
# Install ALL dependencies (needed for build)
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Build stage
|
||||
FROM base AS build
|
||||
|
||||
# ---- development stage ----
|
||||
FROM build AS development
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Define environment variables for development
|
||||
ARG GROQ_API_KEY
|
||||
ARG HuggingFace_API_KEY
|
||||
ARG OPENAI_API_KEY
|
||||
ARG ANTHROPIC_API_KEY
|
||||
ARG OPEN_ROUTER_API_KEY
|
||||
ARG GOOGLE_GENERATIVE_AI_API_KEY
|
||||
ARG OLLAMA_API_BASE_URL
|
||||
ARG XAI_API_KEY
|
||||
ARG TOGETHER_API_KEY
|
||||
ARG TOGETHER_API_BASE_URL
|
||||
ARG VITE_LOG_LEVEL=debug
|
||||
ARG DEFAULT_NUM_CTX
|
||||
# Build the app
|
||||
RUN pnpm run build
|
||||
|
||||
ENV GROQ_API_KEY=${GROQ_API_KEY} \
|
||||
HuggingFace_API_KEY=${HuggingFace_API_KEY} \
|
||||
OPENAI_API_KEY=${OPENAI_API_KEY} \
|
||||
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
|
||||
OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \
|
||||
GOOGLE_GENERATIVE_AI_API_KEY=${GOOGLE_GENERATIVE_AI_API_KEY} \
|
||||
OLLAMA_API_BASE_URL=${OLLAMA_API_BASE_URL} \
|
||||
XAI_API_KEY=${XAI_API_KEY} \
|
||||
TOGETHER_API_KEY=${TOGETHER_API_KEY} \
|
||||
TOGETHER_API_BASE_URL=${TOGETHER_API_BASE_URL} \
|
||||
AWS_BEDROCK_CONFIG=${AWS_BEDROCK_CONFIG} \
|
||||
VITE_LOG_LEVEL=${VITE_LOG_LEVEL} \
|
||||
DEFAULT_NUM_CTX=${DEFAULT_NUM_CTX} \
|
||||
RUNNING_IN_DOCKER=true
|
||||
# Production stage
|
||||
FROM base AS production
|
||||
|
||||
RUN mkdir -p /app/run
|
||||
CMD ["pnpm", "run", "dev", "--host"]
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=8788
|
||||
|
||||
# Copy package files
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
|
||||
# Install production dependencies
|
||||
RUN pnpm install --prod --frozen-lockfile --ignore-scripts
|
||||
|
||||
# Copy built app
|
||||
COPY --from=build /app/build ./build
|
||||
COPY --from=build /app/server.mjs ./server.mjs
|
||||
COPY --from=build /app/public ./public
|
||||
|
||||
EXPOSE 8788
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
|
||||
CMD node -e "require('http').get('http://localhost:8788/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||
|
||||
CMD ["node", "server.mjs"]
|
||||
|
||||
41
README.md
41
README.md
@@ -154,29 +154,52 @@ You have two options for running Bolt.DIY: directly on your machine or using Doc
|
||||
|
||||
### Option 2: Using Docker
|
||||
|
||||
This option requires some familiarity with Docker but provides a more isolated environment.
|
||||
This option requires Docker and is great when you want an isolated environment or to mirror the production image.
|
||||
|
||||
#### Additional Prerequisite
|
||||
|
||||
- Install Docker: [Download Docker](https://www.docker.com/)
|
||||
|
||||
#### Steps:
|
||||
#### Steps
|
||||
|
||||
1. **Build the Docker Image**:
|
||||
1. **Prepare Environment Variables**
|
||||
|
||||
Copy the provided examples and add your provider keys:
|
||||
|
||||
```bash
|
||||
# Using npm script:
|
||||
npm run dockerbuild
|
||||
|
||||
# OR using direct Docker command:
|
||||
docker build . --target bolt-ai-development
|
||||
cp .env.example .env
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
2. **Run the Container**:
|
||||
The runtime scripts inside the container source `.env` and `.env.local`, so keep any API keys you need in one of those files.
|
||||
|
||||
2. **Build an Image**
|
||||
|
||||
```bash
|
||||
# Development image (bind-mounts your local source when run)
|
||||
pnpm run dockerbuild
|
||||
# ≈ docker build -t bolt-ai:development -t bolt-ai:latest --target development .
|
||||
|
||||
# Production image (self-contained build artifacts)
|
||||
pnpm run dockerbuild:prod
|
||||
# ≈ docker build -t bolt-ai:production -t bolt-ai:latest --target bolt-ai-production .
|
||||
```
|
||||
|
||||
3. **Run the Container**
|
||||
|
||||
```bash
|
||||
# Development workflow with hot reload
|
||||
docker compose --profile development up
|
||||
|
||||
# Production-style container using composed services
|
||||
docker compose --profile production up
|
||||
|
||||
# One-off production container (exposes the app on port 5173)
|
||||
docker run --rm -p 5173:5173 --env-file .env.local bolt-ai:latest
|
||||
```
|
||||
|
||||
When the container starts it runs `pnpm run dockerstart`, which in turn executes `bindings.sh` to pass Cloudflare bindings through Wrangler. You can override this command in `docker-compose.yaml` if you need a different startup routine.
|
||||
|
||||
### Option 3: Desktop Application (Electron)
|
||||
|
||||
For users who prefer a native desktop experience, bolt.diy is also available as an Electron desktop application:
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Message } from 'ai';
|
||||
import { useChat } from '@ai-sdk/react';
|
||||
import { useAnimate } from 'framer-motion';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { cssTransition, toast, ToastContainer } from 'react-toastify';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useMessageParser, usePromptEnhancer, useShortcuts } from '~/lib/hooks';
|
||||
import { description, useChatHistory } from '~/lib/persistence';
|
||||
import { chatStore } from '~/lib/stores/chat';
|
||||
@@ -29,11 +29,6 @@ import type { TextUIPart, FileUIPart, Attachment } from '@ai-sdk/ui-utils';
|
||||
import { useMCPStore } from '~/lib/stores/mcp';
|
||||
import type { LlmErrorAlertType } from '~/types/actions';
|
||||
|
||||
const toastAnimation = cssTransition({
|
||||
enter: 'animated fadeInRight',
|
||||
exit: 'animated fadeOutRight',
|
||||
});
|
||||
|
||||
const logger = createScopedLogger('Chat');
|
||||
|
||||
export function Chat() {
|
||||
@@ -56,34 +51,6 @@ export function Chat() {
|
||||
importChat={importChat}
|
||||
/>
|
||||
)}
|
||||
<ToastContainer
|
||||
closeButton={({ closeToast }) => {
|
||||
return (
|
||||
<button className="Toastify__close-button" onClick={closeToast}>
|
||||
<div className="i-ph:x text-lg" />
|
||||
</button>
|
||||
);
|
||||
}}
|
||||
icon={({ type }) => {
|
||||
/**
|
||||
* @todo Handle more types if we need them. This may require extra color palettes.
|
||||
*/
|
||||
switch (type) {
|
||||
case 'success': {
|
||||
return <div className="i-ph:check-bold text-bolt-elements-icon-success text-2xl" />;
|
||||
}
|
||||
case 'error': {
|
||||
return <div className="i-ph:warning-circle-bold text-bolt-elements-icon-error text-2xl" />;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}}
|
||||
position="bottom-right"
|
||||
pauseOnFocusLoss
|
||||
transition={toastAnimation}
|
||||
autoClose={3000}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,6 +145,9 @@ export function useGitHubDeploy() {
|
||||
source: 'github',
|
||||
});
|
||||
|
||||
// Show success toast notification
|
||||
toast.success(`🚀 GitHub deployment preparation completed successfully!`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
files: fileContents,
|
||||
|
||||
@@ -145,6 +145,9 @@ export function useGitLabDeploy() {
|
||||
source: 'gitlab',
|
||||
});
|
||||
|
||||
// Show success toast notification
|
||||
toast.success(`🚀 GitLab deployment preparation completed successfully!`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
files: fileContents,
|
||||
|
||||
@@ -224,6 +224,9 @@ export function useNetlifyDeploy() {
|
||||
source: 'netlify',
|
||||
});
|
||||
|
||||
// Show success toast notification
|
||||
toast.success(`🚀 Netlify deployment completed successfully!`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Deploy error:', error);
|
||||
|
||||
@@ -213,6 +213,9 @@ export function useVercelDeploy() {
|
||||
source: 'vercel',
|
||||
});
|
||||
|
||||
// Show success toast notification
|
||||
toast.success(`🚀 Vercel deployment completed successfully!`);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Vercel deploy error:', err);
|
||||
|
||||
@@ -31,8 +31,7 @@ const buttonVariants = cva(
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
||||
_asChild?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,7 @@ export interface StickToBottomContext {
|
||||
const StickToBottomContext = createContext<StickToBottomContext | null>(null);
|
||||
|
||||
export interface StickToBottomProps
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,
|
||||
StickToBottomOptions {
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>, StickToBottomOptions {
|
||||
contextRef?: React.Ref<StickToBottomContext>;
|
||||
instance?: ReturnType<typeof useStickToBottom>;
|
||||
children: ((context: StickToBottomContext) => ReactNode) | ReactNode;
|
||||
|
||||
@@ -20,20 +20,21 @@ const PREVIEW_CHANNEL = 'preview-updates';
|
||||
export class PreviewsStore {
|
||||
#availablePreviews = new Map<number, PreviewInfo>();
|
||||
#webcontainer: Promise<WebContainer>;
|
||||
#broadcastChannel: BroadcastChannel;
|
||||
#broadcastChannel?: BroadcastChannel;
|
||||
#lastUpdate = new Map<string, number>();
|
||||
#watchedFiles = new Set<string>();
|
||||
#refreshTimeouts = new Map<string, NodeJS.Timeout>();
|
||||
#REFRESH_DELAY = 300;
|
||||
#storageChannel: BroadcastChannel;
|
||||
#storageChannel?: BroadcastChannel;
|
||||
|
||||
previews = atom<PreviewInfo[]>([]);
|
||||
|
||||
constructor(webcontainerPromise: Promise<WebContainer>) {
|
||||
this.#webcontainer = webcontainerPromise;
|
||||
this.#broadcastChannel = new BroadcastChannel(PREVIEW_CHANNEL);
|
||||
this.#storageChannel = new BroadcastChannel('storage-sync-channel');
|
||||
this.#broadcastChannel = this.#maybeCreateChannel(PREVIEW_CHANNEL);
|
||||
this.#storageChannel = this.#maybeCreateChannel('storage-sync-channel');
|
||||
|
||||
if (this.#broadcastChannel) {
|
||||
// Listen for preview updates from other tabs
|
||||
this.#broadcastChannel.onmessage = (event) => {
|
||||
const { type, previewId } = event.data;
|
||||
@@ -48,7 +49,9 @@ export class PreviewsStore {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (this.#storageChannel) {
|
||||
// Listen for storage sync messages
|
||||
this.#storageChannel.onmessage = (event) => {
|
||||
const { storage, source } = event.data;
|
||||
@@ -57,6 +60,7 @@ export class PreviewsStore {
|
||||
this._syncStorage(storage);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Override localStorage setItem to catch all changes
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -71,6 +75,29 @@ export class PreviewsStore {
|
||||
this.#init();
|
||||
}
|
||||
|
||||
#maybeCreateChannel(name: string): BroadcastChannel | undefined {
|
||||
if (typeof globalThis === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const globalBroadcastChannel = (
|
||||
globalThis as typeof globalThis & {
|
||||
BroadcastChannel?: typeof BroadcastChannel;
|
||||
}
|
||||
).BroadcastChannel;
|
||||
|
||||
if (typeof globalBroadcastChannel !== 'function') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return new globalBroadcastChannel(name);
|
||||
} catch (error) {
|
||||
console.warn('[Preview] BroadcastChannel unavailable:', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a unique ID for this tab
|
||||
private _getTabId(): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -130,7 +157,7 @@ export class PreviewsStore {
|
||||
}
|
||||
}
|
||||
|
||||
this.#storageChannel.postMessage({
|
||||
this.#storageChannel?.postMessage({
|
||||
type: 'storage-sync',
|
||||
storage,
|
||||
source: this._getTabId(),
|
||||
@@ -192,7 +219,7 @@ export class PreviewsStore {
|
||||
const timestamp = Date.now();
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
|
||||
this.#broadcastChannel.postMessage({
|
||||
this.#broadcastChannel?.postMessage({
|
||||
type: 'state-change',
|
||||
previewId,
|
||||
timestamp,
|
||||
@@ -204,7 +231,7 @@ export class PreviewsStore {
|
||||
const timestamp = Date.now();
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
|
||||
this.#broadcastChannel.postMessage({
|
||||
this.#broadcastChannel?.postMessage({
|
||||
type: 'file-change',
|
||||
previewId,
|
||||
timestamp,
|
||||
@@ -219,7 +246,7 @@ export class PreviewsStore {
|
||||
const timestamp = Date.now();
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
|
||||
this.#broadcastChannel.postMessage({
|
||||
this.#broadcastChannel?.postMessage({
|
||||
type: 'file-change',
|
||||
previewId,
|
||||
timestamp,
|
||||
|
||||
@@ -52,11 +52,37 @@ export const shortcutsStore = map<Shortcuts>({
|
||||
|
||||
// Create a single key for provider settings
|
||||
const PROVIDER_SETTINGS_KEY = 'provider_settings';
|
||||
const AUTO_ENABLED_KEY = 'auto_enabled_providers';
|
||||
|
||||
// Add this helper function at the top of the file
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
// Initialize provider settings from both localStorage and defaults
|
||||
// Interface for configured provider info from server
|
||||
interface ConfiguredProvider {
|
||||
name: string;
|
||||
isConfigured: boolean;
|
||||
configMethod: 'environment' | 'none';
|
||||
}
|
||||
|
||||
// Fetch configured providers from server
|
||||
const fetchConfiguredProviders = async (): Promise<ConfiguredProvider[]> => {
|
||||
try {
|
||||
const response = await fetch('/api/configured-providers');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { providers?: ConfiguredProvider[] };
|
||||
|
||||
return data.providers || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching configured providers:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize provider settings from both localStorage and server-detected configuration
|
||||
const getInitialProviderSettings = (): ProviderSetting => {
|
||||
const initialSettings: ProviderSetting = {};
|
||||
|
||||
@@ -92,8 +118,84 @@ const getInitialProviderSettings = (): ProviderSetting => {
|
||||
return initialSettings;
|
||||
};
|
||||
|
||||
// Auto-enable providers that are configured on the server
|
||||
const autoEnableConfiguredProviders = async () => {
|
||||
if (!isBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const configuredProviders = await fetchConfiguredProviders();
|
||||
const currentSettings = providersStore.get();
|
||||
const savedSettings = localStorage.getItem(PROVIDER_SETTINGS_KEY);
|
||||
const autoEnabledProviders = localStorage.getItem(AUTO_ENABLED_KEY);
|
||||
|
||||
// Track which providers were auto-enabled to avoid overriding user preferences
|
||||
const previouslyAutoEnabled = autoEnabledProviders ? JSON.parse(autoEnabledProviders) : [];
|
||||
const newlyAutoEnabled: string[] = [];
|
||||
|
||||
let hasChanges = false;
|
||||
|
||||
configuredProviders.forEach(({ name, isConfigured, configMethod }) => {
|
||||
if (isConfigured && configMethod === 'environment' && LOCAL_PROVIDERS.includes(name)) {
|
||||
const currentProvider = currentSettings[name];
|
||||
|
||||
if (currentProvider) {
|
||||
/*
|
||||
* Only auto-enable if:
|
||||
* 1. Provider is not already enabled, AND
|
||||
* 2. Either we haven't saved settings yet (first time) OR provider was previously auto-enabled
|
||||
*/
|
||||
const hasUserSettings = savedSettings !== null;
|
||||
const wasAutoEnabled = previouslyAutoEnabled.includes(name);
|
||||
const shouldAutoEnable = !currentProvider.settings.enabled && (!hasUserSettings || wasAutoEnabled);
|
||||
|
||||
if (shouldAutoEnable) {
|
||||
currentSettings[name] = {
|
||||
...currentProvider,
|
||||
settings: {
|
||||
...currentProvider.settings,
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
newlyAutoEnabled.push(name);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hasChanges) {
|
||||
// Update the store
|
||||
providersStore.set(currentSettings);
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(currentSettings));
|
||||
|
||||
// Update the auto-enabled providers list
|
||||
const allAutoEnabled = [...new Set([...previouslyAutoEnabled, ...newlyAutoEnabled])];
|
||||
localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(allAutoEnabled));
|
||||
|
||||
console.log(`Auto-enabled providers: ${newlyAutoEnabled.join(', ')}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error auto-enabling configured providers:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const providersStore = map<ProviderSetting>(getInitialProviderSettings());
|
||||
|
||||
// Export the auto-enable function for use in components
|
||||
export const initializeProviders = autoEnableConfiguredProviders;
|
||||
|
||||
// Initialize providers when the module loads (in browser only)
|
||||
if (isBrowser) {
|
||||
// Use a small delay to ensure DOM and other resources are ready
|
||||
setTimeout(() => {
|
||||
autoEnableConfiguredProviders();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Create a function to update provider settings that handles both store and persistence
|
||||
export const updateProviderSettings = (provider: string, settings: ProviderSetting) => {
|
||||
const currentSettings = providersStore.get();
|
||||
@@ -113,6 +215,37 @@ export const updateProviderSettings = (provider: string, settings: ProviderSetti
|
||||
// Save to localStorage
|
||||
const allSettings = providersStore.get();
|
||||
localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(allSettings));
|
||||
|
||||
// If this is a local provider, update the auto-enabled tracking
|
||||
if (LOCAL_PROVIDERS.includes(provider) && updatedProvider.settings.enabled !== undefined) {
|
||||
updateAutoEnabledTracking(provider, updatedProvider.settings.enabled);
|
||||
}
|
||||
};
|
||||
|
||||
// Update auto-enabled tracking when user manually changes provider settings
|
||||
const updateAutoEnabledTracking = (providerName: string, isEnabled: boolean) => {
|
||||
if (!isBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const autoEnabledProviders = localStorage.getItem(AUTO_ENABLED_KEY);
|
||||
const currentAutoEnabled = autoEnabledProviders ? JSON.parse(autoEnabledProviders) : [];
|
||||
|
||||
if (isEnabled) {
|
||||
// If user enables provider, add to auto-enabled list (for future detection)
|
||||
if (!currentAutoEnabled.includes(providerName)) {
|
||||
currentAutoEnabled.push(providerName);
|
||||
localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(currentAutoEnabled));
|
||||
}
|
||||
} else {
|
||||
// If user disables provider, remove from auto-enabled list (respect user choice)
|
||||
const updatedAutoEnabled = currentAutoEnabled.filter((name: string) => name !== providerName);
|
||||
localStorage.setItem(AUTO_ENABLED_KEY, JSON.stringify(updatedAutoEnabled));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating auto-enabled tracking:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const isDebugMode = atom(false);
|
||||
|
||||
@@ -51,8 +51,15 @@ export interface SupabaseConnectionState {
|
||||
credentials?: SupabaseCredentials;
|
||||
}
|
||||
|
||||
const savedConnection = typeof localStorage !== 'undefined' ? localStorage.getItem('supabase_connection') : null;
|
||||
const savedCredentials = typeof localStorage !== 'undefined' ? localStorage.getItem('supabaseCredentials') : null;
|
||||
const storage =
|
||||
typeof globalThis !== 'undefined' &&
|
||||
typeof globalThis.localStorage !== 'undefined' &&
|
||||
typeof globalThis.localStorage.getItem === 'function'
|
||||
? globalThis.localStorage
|
||||
: null;
|
||||
|
||||
const savedConnection = storage ? storage.getItem('supabase_connection') : null;
|
||||
const savedCredentials = storage ? storage.getItem('supabaseCredentials') : null;
|
||||
|
||||
const initialState: SupabaseConnectionState = savedConnection
|
||||
? JSON.parse(savedConnection)
|
||||
@@ -75,14 +82,14 @@ if (savedCredentials && !initialState.credentials) {
|
||||
|
||||
export const supabaseConnection = atom<SupabaseConnectionState>(initialState);
|
||||
|
||||
if (initialState.token && !initialState.stats) {
|
||||
fetchSupabaseStats(initialState.token).catch(console.error);
|
||||
}
|
||||
|
||||
export const isConnecting = atom(false);
|
||||
export const isFetchingStats = atom(false);
|
||||
export const isFetchingApiKeys = atom(false);
|
||||
|
||||
if (initialState.token && !initialState.stats) {
|
||||
fetchSupabaseStats(initialState.token).catch(console.error);
|
||||
}
|
||||
|
||||
export function updateSupabaseConnection(connection: Partial<SupabaseConnectionState>) {
|
||||
const currentState = supabaseConnection.get();
|
||||
|
||||
@@ -123,16 +130,16 @@ export function updateSupabaseConnection(connection: Partial<SupabaseConnectionS
|
||||
* Always save the connection state to localStorage to persist across chats
|
||||
*/
|
||||
if (connection.user || connection.token || connection.selectedProjectId !== undefined || connection.credentials) {
|
||||
localStorage.setItem('supabase_connection', JSON.stringify(newState));
|
||||
storage?.setItem('supabase_connection', JSON.stringify(newState));
|
||||
|
||||
if (newState.credentials) {
|
||||
localStorage.setItem('supabaseCredentials', JSON.stringify(newState.credentials));
|
||||
storage?.setItem('supabaseCredentials', JSON.stringify(newState.credentials));
|
||||
} else {
|
||||
localStorage.removeItem('supabaseCredentials');
|
||||
storage?.removeItem('supabaseCredentials');
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem('supabase_connection');
|
||||
localStorage.removeItem('supabaseCredentials');
|
||||
storage?.removeItem('supabase_connection');
|
||||
storage?.removeItem('supabaseCredentials');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
app/root.tsx
31
app/root.tsx
@@ -9,6 +9,7 @@ import { useEffect } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { ClientOnly } from 'remix-utils/client-only';
|
||||
import { cssTransition, ToastContainer } from 'react-toastify';
|
||||
|
||||
import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
|
||||
import globalStyles from './styles/index.scss?url';
|
||||
@@ -16,6 +17,11 @@ import xtermStyles from '@xterm/xterm/css/xterm.css?url';
|
||||
|
||||
import 'virtual:uno.css';
|
||||
|
||||
const toastAnimation = cssTransition({
|
||||
enter: 'animated fadeInRight',
|
||||
exit: 'animated fadeOutRight',
|
||||
});
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{
|
||||
rel: 'icon',
|
||||
@@ -75,6 +81,31 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<ClientOnly>{() => <DndProvider backend={HTML5Backend}>{children}</DndProvider>}</ClientOnly>
|
||||
<ToastContainer
|
||||
closeButton={({ closeToast }) => {
|
||||
return (
|
||||
<button className="Toastify__close-button" onClick={closeToast}>
|
||||
<div className="i-ph:x text-lg" />
|
||||
</button>
|
||||
);
|
||||
}}
|
||||
icon={({ type }) => {
|
||||
switch (type) {
|
||||
case 'success': {
|
||||
return <div className="i-ph:check-bold text-bolt-elements-icon-success text-2xl" />;
|
||||
}
|
||||
case 'error': {
|
||||
return <div className="i-ph:warning-circle-bold text-bolt-elements-icon-error text-2xl" />;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}}
|
||||
position="bottom-right"
|
||||
pauseOnFocusLoss
|
||||
transition={toastAnimation}
|
||||
autoClose={3000}
|
||||
/>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</>
|
||||
|
||||
110
app/routes/api.configured-providers.ts
Normal file
110
app/routes/api.configured-providers.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { LoaderFunction } from '@remix-run/cloudflare';
|
||||
import { json } from '@remix-run/cloudflare';
|
||||
import { LLMManager } from '~/lib/modules/llm/manager';
|
||||
import { LOCAL_PROVIDERS } from '~/lib/stores/settings';
|
||||
|
||||
interface ConfiguredProvider {
|
||||
name: string;
|
||||
isConfigured: boolean;
|
||||
configMethod: 'environment' | 'none';
|
||||
}
|
||||
|
||||
interface ConfiguredProvidersResponse {
|
||||
providers: ConfiguredProvider[];
|
||||
}
|
||||
|
||||
/**
|
||||
* API endpoint that detects which providers are configured via environment variables
|
||||
* This helps auto-enable providers that have been set up by the user
|
||||
*/
|
||||
export const loader: LoaderFunction = async ({ context }) => {
|
||||
try {
|
||||
const llmManager = LLMManager.getInstance(context?.cloudflare?.env as any);
|
||||
const configuredProviders: ConfiguredProvider[] = [];
|
||||
|
||||
// Check each local provider for environment configuration
|
||||
for (const providerName of LOCAL_PROVIDERS) {
|
||||
const providerInstance = llmManager.getProvider(providerName);
|
||||
let isConfigured = false;
|
||||
let configMethod: 'environment' | 'none' = 'none';
|
||||
|
||||
if (providerInstance) {
|
||||
const config = providerInstance.config;
|
||||
|
||||
/*
|
||||
* Check if required environment variables are set
|
||||
* For providers with baseUrlKey (Ollama, LMStudio, OpenAILike)
|
||||
*/
|
||||
if (config.baseUrlKey) {
|
||||
const baseUrlEnvVar = config.baseUrlKey;
|
||||
const cloudflareEnv = (context?.cloudflare?.env as Record<string, any>)?.[baseUrlEnvVar];
|
||||
const processEnv = process.env[baseUrlEnvVar];
|
||||
const managerEnv = llmManager.env[baseUrlEnvVar];
|
||||
|
||||
const envBaseUrl = cloudflareEnv || processEnv || managerEnv;
|
||||
|
||||
/*
|
||||
* Only consider configured if environment variable is explicitly set
|
||||
* Don't count default config.baseUrl values or placeholder values
|
||||
*/
|
||||
const isValidEnvValue =
|
||||
envBaseUrl &&
|
||||
typeof envBaseUrl === 'string' &&
|
||||
envBaseUrl.trim().length > 0 &&
|
||||
!envBaseUrl.includes('your_') && // Filter out placeholder values like "your_openai_like_base_url_here"
|
||||
!envBaseUrl.includes('_here') &&
|
||||
envBaseUrl.startsWith('http'); // Must be a valid URL
|
||||
|
||||
if (isValidEnvValue) {
|
||||
isConfigured = true;
|
||||
configMethod = 'environment';
|
||||
}
|
||||
}
|
||||
|
||||
// For providers that might need API keys as well (check this separately, not as fallback)
|
||||
if (config.apiTokenKey && !isConfigured) {
|
||||
const apiTokenEnvVar = config.apiTokenKey;
|
||||
const envApiToken =
|
||||
(context?.cloudflare?.env as Record<string, any>)?.[apiTokenEnvVar] ||
|
||||
process.env[apiTokenEnvVar] ||
|
||||
llmManager.env[apiTokenEnvVar];
|
||||
|
||||
// Only consider configured if API key is set and not a placeholder
|
||||
const isValidApiToken =
|
||||
envApiToken &&
|
||||
typeof envApiToken === 'string' &&
|
||||
envApiToken.trim().length > 0 &&
|
||||
!envApiToken.includes('your_') && // Filter out placeholder values
|
||||
!envApiToken.includes('_here') &&
|
||||
envApiToken.length > 10; // API keys are typically longer than 10 chars
|
||||
|
||||
if (isValidApiToken) {
|
||||
isConfigured = true;
|
||||
configMethod = 'environment';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuredProviders.push({
|
||||
name: providerName,
|
||||
isConfigured,
|
||||
configMethod,
|
||||
});
|
||||
}
|
||||
|
||||
return json<ConfiguredProvidersResponse>({
|
||||
providers: configuredProviders,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error detecting configured providers:', error);
|
||||
|
||||
// Return default state on error
|
||||
return json<ConfiguredProvidersResponse>({
|
||||
providers: LOCAL_PROVIDERS.map((name) => ({
|
||||
name,
|
||||
isConfigured: false,
|
||||
configMethod: 'none' as const,
|
||||
})),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import { json, type LoaderFunctionArgs } from '@remix-run/cloudflare';
|
||||
|
||||
export const loader = async ({ request: _request }: LoaderFunctionArgs) => {
|
||||
return json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
export function loader() {
|
||||
return new Response("OK", {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,8 +46,10 @@ export default function WebContainerPreview() {
|
||||
}, [previewId, previewUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize broadcast channel
|
||||
broadcastChannelRef.current = new BroadcastChannel(PREVIEW_CHANNEL);
|
||||
const supportsBroadcastChannel = typeof window !== 'undefined' && typeof window.BroadcastChannel === 'function';
|
||||
|
||||
if (supportsBroadcastChannel) {
|
||||
broadcastChannelRef.current = new window.BroadcastChannel(PREVIEW_CHANNEL);
|
||||
|
||||
// Listen for preview updates
|
||||
broadcastChannelRef.current.onmessage = (event) => {
|
||||
@@ -57,6 +59,9 @@ export default function WebContainerPreview() {
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
broadcastChannelRef.current = undefined;
|
||||
}
|
||||
|
||||
// Construct the WebContainer preview URL
|
||||
const url = `https://${previewId}.local-credentialless.webcontainer-api.io`;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
@use '../z-index';
|
||||
|
||||
.Toastify__toast-container {
|
||||
@extend .z-toast;
|
||||
}
|
||||
|
||||
.Toastify__toast {
|
||||
--at-apply: shadow-md;
|
||||
|
||||
|
||||
@@ -31,3 +31,7 @@ $zIndexMax: 999;
|
||||
.z-max {
|
||||
z-index: $zIndexMax;
|
||||
}
|
||||
|
||||
.z-toast {
|
||||
z-index: $zIndexMax + 1;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ services:
|
||||
app-dev:
|
||||
image: bolt-ai:development
|
||||
build:
|
||||
target: bolt-ai-development
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: development
|
||||
env_file:
|
||||
- '.env'
|
||||
- '.env.local'
|
||||
|
||||
9
package-additions.json
Normal file
9
package-additions.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@remix-run/express": "^2.13.1",
|
||||
"@remix-run/node": "^2.13.1",
|
||||
"express": "^4.19.2",
|
||||
"compression": "^1.7.4",
|
||||
"morgan": "^1.10.0"
|
||||
}
|
||||
}
|
||||
34848
package-lock.json
generated
Normal file
34848
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -24,11 +24,10 @@
|
||||
"dockerstart": "bindings=$(./bindings.sh) && wrangler pages dev ./build/client $bindings --ip 0.0.0.0 --port 5173 --no-show-interactive-dev-session",
|
||||
"dockerrun": "docker run -it -d --name bolt-ai-live -p 5173:5173 --env-file .env.local bolt-ai",
|
||||
"dockerbuild:prod": "docker build -t bolt-ai:production -t bolt-ai:latest --target bolt-ai-production .",
|
||||
"dockerbuild": "docker build -t bolt-ai:development -t bolt-ai:latest --target bolt-ai-development .",
|
||||
"dockerbuild": "docker build -t bolt-ai:development -t bolt-ai:latest --target development .",
|
||||
"typecheck": "tsc",
|
||||
"typegen": "wrangler types",
|
||||
"preview": "pnpm run build && pnpm run start",
|
||||
"prepare": "husky",
|
||||
"clean": "node scripts/clean.js",
|
||||
"electron:dev": "node scripts/electron-dev.mjs",
|
||||
"electron:dev:inspect": "NODE_ENV=development electron --inspect=9229 build/electron/main/index.mjs",
|
||||
@@ -40,7 +39,8 @@
|
||||
"electron:build:mac": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --mac",
|
||||
"electron:build:win": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --win",
|
||||
"electron:build:linux": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --linux",
|
||||
"electron:build:dist": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --mwl"
|
||||
"electron:build:dist": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --mwl",
|
||||
"start:prod": "node server.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
@@ -96,7 +96,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@remix-run/cloudflare": "^2.15.2",
|
||||
"@remix-run/cloudflare-pages": "^2.15.2",
|
||||
"@remix-run/node": "^2.15.2",
|
||||
"@remix-run/node": "^2.13.1",
|
||||
"@remix-run/react": "^2.15.2",
|
||||
"@tanstack/react-virtual": "^3.13.0",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
@@ -157,11 +157,15 @@
|
||||
"use-debounce": "^10.0.4",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"zod": "^3.24.1",
|
||||
"zustand": "^5.0.3"
|
||||
"zustand": "^5.0.3",
|
||||
"@remix-run/express": "^2.13.1",
|
||||
"express": "^4.19.2",
|
||||
"compression": "^1.7.4",
|
||||
"morgan": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitz/eslint-plugin": "0.1.0",
|
||||
"@cloudflare/workers-types": "^4.20241127.0",
|
||||
"@cloudflare/workers-types": "^4.20251011.0",
|
||||
"@electron/notarize": "^2.5.0",
|
||||
"@iconify-json/ph": "^1.2.1",
|
||||
"@iconify/types": "^2.0.0",
|
||||
@@ -204,7 +208,7 @@
|
||||
"vite-plugin-optimize-css-modules": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^2.1.7",
|
||||
"wrangler": "^4.5.1"
|
||||
"wrangler": "^4.44.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@typescript-eslint/utils": "^8.0.0-alpha.30"
|
||||
|
||||
152
pnpm-lock.yaml
generated
152
pnpm-lock.yaml
generated
@@ -157,10 +157,10 @@ importers:
|
||||
version: 1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@remix-run/cloudflare':
|
||||
specifier: ^2.15.2
|
||||
version: 2.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3)
|
||||
version: 2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3)
|
||||
'@remix-run/cloudflare-pages':
|
||||
specifier: ^2.15.2
|
||||
version: 2.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3)
|
||||
version: 2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3)
|
||||
'@remix-run/node':
|
||||
specifier: ^2.15.2
|
||||
version: 2.16.8(typescript@5.8.3)
|
||||
@@ -322,7 +322,7 @@ importers:
|
||||
version: 0.2.0(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/server-runtime@2.16.8(typescript@5.8.3))(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.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3))(@remix-run/node@2.16.8(typescript@5.8.3))(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/router@1.23.0)(react@18.3.1)(zod@3.25.76)
|
||||
version: 7.7.0(@remix-run/cloudflare@2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3))(@remix-run/node@2.16.8(typescript@5.8.3))(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/router@1.23.0)(react@18.3.1)(zod@3.25.76)
|
||||
rollup-plugin-node-polyfills:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1
|
||||
@@ -352,8 +352,8 @@ importers:
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0(jiti@1.21.7)(prettier@3.6.2)(typescript@5.8.3)
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20241127.0
|
||||
version: 4.20250722.0
|
||||
specifier: ^4.20251011.0
|
||||
version: 4.20251014.0
|
||||
'@electron/notarize':
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0
|
||||
@@ -365,7 +365,7 @@ importers:
|
||||
version: 2.0.0
|
||||
'@remix-run/dev':
|
||||
specifier: ^2.15.2
|
||||
version: 2.16.8(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.8(typescript@5.8.3))(@types/node@24.1.0)(sass-embedded@1.89.2)(typescript@5.8.3)(vite@5.4.19(@types/node@24.1.0)(sass-embedded@1.89.2))(wrangler@4.25.1(@cloudflare/workers-types@4.20250722.0))
|
||||
version: 2.16.8(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.8(typescript@5.8.3))(@types/node@24.1.0)(sass-embedded@1.89.2)(typescript@5.8.3)(vite@5.4.19(@types/node@24.1.0)(sass-embedded@1.89.2))(wrangler@4.44.0(@cloudflare/workers-types@4.20251014.0))
|
||||
'@remix-run/serve':
|
||||
specifier: ^2.15.2
|
||||
version: 2.16.8(typescript@5.8.3)
|
||||
@@ -419,7 +419,7 @@ importers:
|
||||
version: 33.4.11
|
||||
electron-builder:
|
||||
specifier: ^26.0.12
|
||||
version: 26.0.12(electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12))
|
||||
version: 26.0.12(electron-builder-squirrel-windows@26.0.12)
|
||||
eslint-config-prettier:
|
||||
specifier: ^10.1.1
|
||||
version: 10.1.8(eslint@9.31.0(jiti@1.21.7))
|
||||
@@ -481,8 +481,8 @@ importers:
|
||||
specifier: ^2.1.7
|
||||
version: 2.1.9(@types/node@24.1.0)(jsdom@26.1.0)(sass-embedded@1.89.2)
|
||||
wrangler:
|
||||
specifier: ^4.5.1
|
||||
version: 4.25.1(@cloudflare/workers-types@4.20250722.0)
|
||||
specifier: ^4.44.0
|
||||
version: 4.44.0(@cloudflare/workers-types@4.20251014.0)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -954,47 +954,47 @@ packages:
|
||||
resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@cloudflare/unenv-preset@2.3.3':
|
||||
resolution: {integrity: sha512-/M3MEcj3V2WHIRSW1eAQBPRJ6JnGQHc6JKMAPLkDb7pLs3m6X9ES/+K3ceGqxI6TKeF32AWAi7ls0AYzVxCP0A==}
|
||||
'@cloudflare/unenv-preset@2.7.8':
|
||||
resolution: {integrity: sha512-Ky929MfHh+qPhwCapYrRPwPVHtA2Ioex/DbGZyskGyNRDe9Ru3WThYZivyNVaPy5ergQSgMs9OKrM9Ajtz9F6w==}
|
||||
peerDependencies:
|
||||
unenv: 2.0.0-rc.17
|
||||
workerd: ^1.20250508.0
|
||||
unenv: 2.0.0-rc.21
|
||||
workerd: ^1.20250927.0
|
||||
peerDependenciesMeta:
|
||||
workerd:
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-darwin-64@1.20250712.0':
|
||||
resolution: {integrity: sha512-M6S6a/LQ0Jb0R+g0XhlYi1adGifvYmxA5mD/i9TuZZgjs2bIm5ELuka/n3SCnI98ltvlx3HahRaHagAtOilsFg==}
|
||||
'@cloudflare/workerd-darwin-64@1.20251011.0':
|
||||
resolution: {integrity: sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@cloudflare/workerd-darwin-arm64@1.20250712.0':
|
||||
resolution: {integrity: sha512-7sFzn6rvAcnLy7MktFL42dYtzL0Idw/kiUmNf2P3TvsBRoShhLK5ZKhbw+NAhvU8e4pXWm5lkE0XmpieA0zNjw==}
|
||||
'@cloudflare/workerd-darwin-arm64@1.20251011.0':
|
||||
resolution: {integrity: sha512-1WuFBGwZd15p4xssGN/48OE2oqokIuc51YvHvyNivyV8IYnAs3G9bJNGWth1X7iMDPe4g44pZrKhRnISS2+5dA==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@cloudflare/workerd-linux-64@1.20250712.0':
|
||||
resolution: {integrity: sha512-EFRrGe/bqK7NHtht7vNlbrDpfvH3eRvtJOgsTpEQEysDjVmlK6pVJxSnLy9Hg1zlLY15IfhfGC+K2qisseHGJQ==}
|
||||
'@cloudflare/workerd-linux-64@1.20251011.0':
|
||||
resolution: {integrity: sha512-BccMiBzFlWZyFghIw2szanmYJrJGBGHomw2y/GV6pYXChFzMGZkeCEMfmCyJj29xczZXxcZmUVJxNy4eJxO8QA==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@cloudflare/workerd-linux-arm64@1.20250712.0':
|
||||
resolution: {integrity: sha512-rG8JUleddhUHQVwpXOYv0VbL0S9kOtR9PNKecgVhFpxEhC8aTeg2HNBBjo8st7IfcUvY8WaW3pD3qdAMZ05UwQ==}
|
||||
'@cloudflare/workerd-linux-arm64@1.20251011.0':
|
||||
resolution: {integrity: sha512-79o/216lsbAbKEVDZYXR24ivEIE2ysDL9jvo0rDTkViLWju9dAp3CpyetglpJatbSi3uWBPKZBEOqN68zIjVsQ==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@cloudflare/workerd-windows-64@1.20250712.0':
|
||||
resolution: {integrity: sha512-qS8H5RCYwE21Om9wo5/F807ClBJIfknhuLBj16eYxvJcj9JqgAKWi12BGgjyGxHuJJjeoQ63lr4wHAdbFntDDg==}
|
||||
'@cloudflare/workerd-windows-64@1.20251011.0':
|
||||
resolution: {integrity: sha512-RIXUQRchFdqEvaUqn1cXZXSKjpqMaSaVAkI5jNZ8XzAw/bw2bcdOVUtakrflgxDprltjFb0PTNtuss1FKtH9Jg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@cloudflare/workers-types@4.20250722.0':
|
||||
resolution: {integrity: sha512-pTY+A07DTSacgUBYcVEEb78/KG7THdcRpPqXLeH/A/LHHobAddgN4zyXBldsoZuzy7bD9tZYJW+wkcyR4k7fDA==}
|
||||
'@cloudflare/workers-types@4.20251014.0':
|
||||
resolution: {integrity: sha512-tEW98J/kOa0TdylIUOrLKRdwkUw0rvvYVlo+Ce0mqRH3c8kSoxLzUH9gfCvwLe0M89z1RkzFovSKAW2Nwtyn3w==}
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
|
||||
@@ -6068,8 +6068,8 @@ packages:
|
||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
miniflare@4.20250712.1:
|
||||
resolution: {integrity: sha512-46gB3FGPOsy+EpFGufjhr8agYycO/55d6l0y7hNJ13NcTVwrObMg/0HmI3pC5yQj0974IVXzBgUfDBMAX6thow==}
|
||||
miniflare@4.20251011.0:
|
||||
resolution: {integrity: sha512-DlZ7vR5q/RE9eLsxsrXzfSZIF2f6O5k0YsFrSKhWUtdefyGtJt4sSpR6V+Af/waaZ6+zIFy9lsknHBCm49sEYA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -7765,12 +7765,12 @@ packages:
|
||||
resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==}
|
||||
engines: {node: '>=18.17'}
|
||||
|
||||
undici@7.12.0:
|
||||
resolution: {integrity: sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==}
|
||||
undici@7.14.0:
|
||||
resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
unenv@2.0.0-rc.17:
|
||||
resolution: {integrity: sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg==}
|
||||
unenv@2.0.0-rc.21:
|
||||
resolution: {integrity: sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==}
|
||||
|
||||
unified@10.1.2:
|
||||
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
|
||||
@@ -8137,17 +8137,17 @@ packages:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
workerd@1.20250712.0:
|
||||
resolution: {integrity: sha512-7h+k1OxREpiZW0849g0uQNexRWMcs5i5gUGhJzCY8nIx6Tv4D/ndlXJ47lEFj7/LQdp165IL9dM2D5uDiedZrg==}
|
||||
workerd@1.20251011.0:
|
||||
resolution: {integrity: sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
wrangler@4.25.1:
|
||||
resolution: {integrity: sha512-4Tlg+jmqxCX3xFm+Nz1b4jHHY9iOu1EyJ17SSCCJ6MGp+FCGtXgr+CynT94+MP0v/qKQUkMKjoeJ5FNDunZ9cA==}
|
||||
wrangler@4.44.0:
|
||||
resolution: {integrity: sha512-BLOUigckcWZ0r4rm7b5PuaTpb9KP9as0XeCRSJ8kqcNgXcKoUD3Ij8FlPvN25KybLnFnetaO0ZdfRYUPWle4qw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@cloudflare/workers-types': ^4.20250712.0
|
||||
'@cloudflare/workers-types': ^4.20251011.0
|
||||
peerDependenciesMeta:
|
||||
'@cloudflare/workers-types':
|
||||
optional: true
|
||||
@@ -9104,28 +9104,28 @@ snapshots:
|
||||
dependencies:
|
||||
mime: 3.0.0
|
||||
|
||||
'@cloudflare/unenv-preset@2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250712.0)':
|
||||
'@cloudflare/unenv-preset@2.7.8(unenv@2.0.0-rc.21)(workerd@1.20251011.0)':
|
||||
dependencies:
|
||||
unenv: 2.0.0-rc.17
|
||||
unenv: 2.0.0-rc.21
|
||||
optionalDependencies:
|
||||
workerd: 1.20250712.0
|
||||
workerd: 1.20251011.0
|
||||
|
||||
'@cloudflare/workerd-darwin-64@1.20250712.0':
|
||||
'@cloudflare/workerd-darwin-64@1.20251011.0':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-darwin-arm64@1.20250712.0':
|
||||
'@cloudflare/workerd-darwin-arm64@1.20251011.0':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-linux-64@1.20250712.0':
|
||||
'@cloudflare/workerd-linux-64@1.20251011.0':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-linux-arm64@1.20250712.0':
|
||||
'@cloudflare/workerd-linux-arm64@1.20251011.0':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-windows-64@1.20250712.0':
|
||||
'@cloudflare/workerd-windows-64@1.20251011.0':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workers-types@4.20250722.0': {}
|
||||
'@cloudflare/workers-types@4.20251014.0': {}
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
dependencies:
|
||||
@@ -10765,22 +10765,22 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@remix-run/cloudflare-pages@2.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3)':
|
||||
'@remix-run/cloudflare-pages@2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@cloudflare/workers-types': 4.20250722.0
|
||||
'@remix-run/cloudflare': 2.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3)
|
||||
'@cloudflare/workers-types': 4.20251014.0
|
||||
'@remix-run/cloudflare': 2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
'@remix-run/cloudflare@2.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3)':
|
||||
'@remix-run/cloudflare@2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.1.3
|
||||
'@cloudflare/workers-types': 4.20250722.0
|
||||
'@cloudflare/workers-types': 4.20251014.0
|
||||
'@remix-run/server-runtime': 2.16.8(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
'@remix-run/dev@2.16.8(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.8(typescript@5.8.3))(@types/node@24.1.0)(sass-embedded@1.89.2)(typescript@5.8.3)(vite@5.4.19(@types/node@24.1.0)(sass-embedded@1.89.2))(wrangler@4.25.1(@cloudflare/workers-types@4.20250722.0))':
|
||||
'@remix-run/dev@2.16.8(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.8(typescript@5.8.3))(@types/node@24.1.0)(sass-embedded@1.89.2)(typescript@5.8.3)(vite@5.4.19(@types/node@24.1.0)(sass-embedded@1.89.2))(wrangler@4.44.0(@cloudflare/workers-types@4.20251014.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/generator': 7.28.0
|
||||
@@ -10843,7 +10843,7 @@ snapshots:
|
||||
'@remix-run/serve': 2.16.8(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
vite: 5.4.19(@types/node@24.1.0)(sass-embedded@1.89.2)
|
||||
wrangler: 4.25.1(@cloudflare/workers-types@4.20250722.0)
|
||||
wrangler: 4.44.0(@cloudflare/workers-types@4.20251014.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
@@ -12074,7 +12074,7 @@ snapshots:
|
||||
|
||||
app-builder-bin@5.0.0-alpha.12: {}
|
||||
|
||||
app-builder-lib@26.0.12(dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12))(electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12)):
|
||||
app-builder-lib@26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@26.0.12):
|
||||
dependencies:
|
||||
'@develar/schema-utils': 2.6.5
|
||||
'@electron/asar': 3.2.18
|
||||
@@ -12887,7 +12887,7 @@ snapshots:
|
||||
|
||||
dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12):
|
||||
dependencies:
|
||||
app-builder-lib: 26.0.12(dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12))(electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12))
|
||||
app-builder-lib: 26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@26.0.12)
|
||||
builder-util: 26.0.11
|
||||
builder-util-runtime: 9.3.1
|
||||
fs-extra: 10.1.0
|
||||
@@ -12966,7 +12966,7 @@ snapshots:
|
||||
|
||||
electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12):
|
||||
dependencies:
|
||||
app-builder-lib: 26.0.12(dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12))(electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12))
|
||||
app-builder-lib: 26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@26.0.12)
|
||||
builder-util: 26.0.11
|
||||
electron-winstaller: 5.4.0
|
||||
transitivePeerDependencies:
|
||||
@@ -12974,9 +12974,9 @@ snapshots:
|
||||
- dmg-builder
|
||||
- supports-color
|
||||
|
||||
electron-builder@26.0.12(electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12)):
|
||||
electron-builder@26.0.12(electron-builder-squirrel-windows@26.0.12):
|
||||
dependencies:
|
||||
app-builder-lib: 26.0.12(dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12))(electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12))
|
||||
app-builder-lib: 26.0.12(dmg-builder@26.0.12)(electron-builder-squirrel-windows@26.0.12)
|
||||
builder-util: 26.0.11
|
||||
builder-util-runtime: 9.3.1
|
||||
chalk: 4.1.2
|
||||
@@ -15269,7 +15269,7 @@ snapshots:
|
||||
|
||||
min-indent@1.0.1: {}
|
||||
|
||||
miniflare@4.20250712.1:
|
||||
miniflare@4.20251011.0:
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
acorn: 8.14.0
|
||||
@@ -15278,8 +15278,8 @@ snapshots:
|
||||
glob-to-regexp: 0.4.1
|
||||
sharp: 0.33.5
|
||||
stoppable: 1.1.0
|
||||
undici: 7.12.0
|
||||
workerd: 1.20250712.0
|
||||
undici: 7.14.0
|
||||
workerd: 1.20251011.0
|
||||
ws: 8.18.0
|
||||
youch: 4.1.0-beta.10
|
||||
zod: 3.22.3
|
||||
@@ -16229,11 +16229,11 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
remix-utils@7.7.0(@remix-run/cloudflare@2.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3))(@remix-run/node@2.16.8(typescript@5.8.3))(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/router@1.23.0)(react@18.3.1)(zod@3.25.76):
|
||||
remix-utils@7.7.0(@remix-run/cloudflare@2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3))(@remix-run/node@2.16.8(typescript@5.8.3))(@remix-run/react@2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/router@1.23.0)(react@18.3.1)(zod@3.25.76):
|
||||
dependencies:
|
||||
type-fest: 4.41.0
|
||||
optionalDependencies:
|
||||
'@remix-run/cloudflare': 2.16.8(@cloudflare/workers-types@4.20250722.0)(typescript@5.8.3)
|
||||
'@remix-run/cloudflare': 2.16.8(@cloudflare/workers-types@4.20251014.0)(typescript@5.8.3)
|
||||
'@remix-run/node': 2.16.8(typescript@5.8.3)
|
||||
'@remix-run/react': 2.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)
|
||||
'@remix-run/router': 1.23.0
|
||||
@@ -17072,9 +17072,9 @@ snapshots:
|
||||
|
||||
undici@6.21.3: {}
|
||||
|
||||
undici@7.12.0: {}
|
||||
undici@7.14.0: {}
|
||||
|
||||
unenv@2.0.0-rc.17:
|
||||
unenv@2.0.0-rc.21:
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
exsolve: 1.0.7
|
||||
@@ -17522,26 +17522,26 @@ snapshots:
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
workerd@1.20250712.0:
|
||||
workerd@1.20251011.0:
|
||||
optionalDependencies:
|
||||
'@cloudflare/workerd-darwin-64': 1.20250712.0
|
||||
'@cloudflare/workerd-darwin-arm64': 1.20250712.0
|
||||
'@cloudflare/workerd-linux-64': 1.20250712.0
|
||||
'@cloudflare/workerd-linux-arm64': 1.20250712.0
|
||||
'@cloudflare/workerd-windows-64': 1.20250712.0
|
||||
'@cloudflare/workerd-darwin-64': 1.20251011.0
|
||||
'@cloudflare/workerd-darwin-arm64': 1.20251011.0
|
||||
'@cloudflare/workerd-linux-64': 1.20251011.0
|
||||
'@cloudflare/workerd-linux-arm64': 1.20251011.0
|
||||
'@cloudflare/workerd-windows-64': 1.20251011.0
|
||||
|
||||
wrangler@4.25.1(@cloudflare/workers-types@4.20250722.0):
|
||||
wrangler@4.44.0(@cloudflare/workers-types@4.20251014.0):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.4.0
|
||||
'@cloudflare/unenv-preset': 2.3.3(unenv@2.0.0-rc.17)(workerd@1.20250712.0)
|
||||
'@cloudflare/unenv-preset': 2.7.8(unenv@2.0.0-rc.21)(workerd@1.20251011.0)
|
||||
blake3-wasm: 2.1.5
|
||||
esbuild: 0.25.4
|
||||
miniflare: 4.20250712.1
|
||||
miniflare: 4.20251011.0
|
||||
path-to-regexp: 6.3.0
|
||||
unenv: 2.0.0-rc.17
|
||||
workerd: 1.20250712.0
|
||||
unenv: 2.0.0-rc.21
|
||||
workerd: 1.20251011.0
|
||||
optionalDependencies:
|
||||
'@cloudflare/workers-types': 4.20250722.0
|
||||
'@cloudflare/workers-types': 4.20251014.0
|
||||
fsevents: 2.3.3
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
|
||||
27
server.js
Normal file
27
server.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { createRequestHandler } = require("@remix-run/express");
|
||||
const express = require("express");
|
||||
const path = require("path");
|
||||
|
||||
const BUILD_DIR = path.join(process.cwd(), "build/server");
|
||||
|
||||
const app = express();
|
||||
|
||||
// Serve static files
|
||||
app.use(express.static("build/client", {
|
||||
maxAge: "1y",
|
||||
immutable: true,
|
||||
}));
|
||||
|
||||
// Handle Remix requests
|
||||
app.all(
|
||||
"*",
|
||||
createRequestHandler({
|
||||
build: require(BUILD_DIR),
|
||||
mode: process.env.NODE_ENV,
|
||||
})
|
||||
);
|
||||
|
||||
const port = process.env.PORT || 8788;
|
||||
app.listen(port, "0.0.0.0", () => {
|
||||
console.log(`✅ Bolt.diy running on http://0.0.0.0:${port}`);
|
||||
});
|
||||
40
server.mjs
Normal file
40
server.mjs
Normal file
@@ -0,0 +1,40 @@
|
||||
import { createRequestHandler } from "@remix-run/express";
|
||||
import { installGlobals } from "@remix-run/node";
|
||||
import compression from "compression";
|
||||
import express from "express";
|
||||
import morgan from "morgan";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
installGlobals();
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const BUILD_PATH = join(__dirname, "build", "server", "index.js");
|
||||
const PUBLIC_PATH = join(__dirname, "build", "client");
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(compression());
|
||||
app.disable("x-powered-by");
|
||||
app.use(morgan("tiny"));
|
||||
|
||||
app.use(
|
||||
express.static(PUBLIC_PATH, {
|
||||
maxAge: "1h",
|
||||
immutable: true,
|
||||
})
|
||||
);
|
||||
|
||||
app.all(
|
||||
"*",
|
||||
createRequestHandler({
|
||||
build: await import(BUILD_PATH),
|
||||
})
|
||||
);
|
||||
|
||||
const port = process.env.PORT || 8788;
|
||||
app.listen(port, "0.0.0.0", () => {
|
||||
console.log(`Express server listening on port ${port}`);
|
||||
});
|
||||
Reference in New Issue
Block a user