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
|
- name: Validate Docker production build
|
||||||
run: |
|
run: |
|
||||||
echo "🐳 Testing Docker production target..."
|
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"
|
echo "✅ Production target builds successfully"
|
||||||
|
|
||||||
- name: Validate Docker development build
|
- name: Validate Docker development build
|
||||||
|
|||||||
2
.github/workflows/docker.yaml
vendored
2
.github/workflows/docker.yaml
vendored
@@ -57,7 +57,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
target: runtime
|
target: bolt-ai-production
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
122
Dockerfile
122
Dockerfile
@@ -1,92 +1,56 @@
|
|||||||
# ---- build stage ----
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:22-bookworm-slim AS build
|
FROM node:22-bookworm-slim AS base
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# CI-friendly env
|
# Install pnpm
|
||||||
ENV HUSKY=0
|
|
||||||
ENV CI=true
|
|
||||||
|
|
||||||
# Use pnpm
|
|
||||||
RUN corepack enable && corepack prepare pnpm@9.15.9 --activate
|
RUN corepack enable && corepack prepare pnpm@9.15.9 --activate
|
||||||
|
|
||||||
# Accept (optional) build-time public URL for Remix/Vite (Coolify can pass it)
|
# Install system dependencies
|
||||||
ARG VITE_PUBLIC_APP_URL
|
RUN apt-get update && \
|
||||||
ENV VITE_PUBLIC_APP_URL=${VITE_PUBLIC_APP_URL}
|
apt-get install -y --no-install-recommends \
|
||||||
|
git \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 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
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Dependencies stage
|
||||||
|
FROM base AS deps
|
||||||
|
|
||||||
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Install ALL dependencies (needed for build)
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Build stage
|
||||||
|
FROM base AS build
|
||||||
|
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the app
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM base AS production
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV PORT=3000
|
ENV PORT=8788
|
||||||
ENV HOST=0.0.0.0
|
|
||||||
|
|
||||||
# Install curl so Coolify’s healthcheck works inside the image
|
# Copy package files
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends curl \
|
COPY package.json pnpm-lock.yaml ./
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Copy only what we need to run
|
# Install production dependencies
|
||||||
COPY --from=build /app/build /app/build
|
RUN pnpm install --prod --frozen-lockfile --ignore-scripts
|
||||||
COPY --from=build /app/node_modules /app/node_modules
|
|
||||||
COPY --from=build /app/package.json /app/package.json
|
|
||||||
|
|
||||||
EXPOSE 3000
|
# Copy built app
|
||||||
|
COPY --from=build /app/build ./build
|
||||||
|
COPY --from=build /app/server.mjs ./server.mjs
|
||||||
|
COPY --from=build /app/public ./public
|
||||||
|
|
||||||
# Healthcheck for Coolify
|
EXPOSE 8788
|
||||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=5 \
|
|
||||||
CMD curl -fsS http://localhost:3000/ || exit 1
|
|
||||||
|
|
||||||
# Start the Remix server
|
# Health check
|
||||||
CMD ["node", "build/server/index.js"]
|
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"]
|
||||||
# ---- development stage ----
|
|
||||||
FROM build AS development
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
RUN mkdir -p /app/run
|
|
||||||
CMD ["pnpm", "run", "dev", "--host"]
|
|
||||||
|
|||||||
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
|
### 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
|
#### Additional Prerequisite
|
||||||
|
|
||||||
- Install Docker: [Download Docker](https://www.docker.com/)
|
- 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
|
```bash
|
||||||
# Using npm script:
|
cp .env.example .env
|
||||||
npm run dockerbuild
|
cp .env.example .env.local
|
||||||
|
|
||||||
# OR using direct Docker command:
|
|
||||||
docker build . --target bolt-ai-development
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
```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
|
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)
|
### Option 3: Desktop Application (Electron)
|
||||||
|
|
||||||
For users who prefer a native desktop experience, bolt.diy is also available as an Electron desktop application:
|
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 { useChat } from '@ai-sdk/react';
|
||||||
import { useAnimate } from 'framer-motion';
|
import { useAnimate } from 'framer-motion';
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
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 { useMessageParser, usePromptEnhancer, useShortcuts } from '~/lib/hooks';
|
||||||
import { description, useChatHistory } from '~/lib/persistence';
|
import { description, useChatHistory } from '~/lib/persistence';
|
||||||
import { chatStore } from '~/lib/stores/chat';
|
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 { useMCPStore } from '~/lib/stores/mcp';
|
||||||
import type { LlmErrorAlertType } from '~/types/actions';
|
import type { LlmErrorAlertType } from '~/types/actions';
|
||||||
|
|
||||||
const toastAnimation = cssTransition({
|
|
||||||
enter: 'animated fadeInRight',
|
|
||||||
exit: 'animated fadeOutRight',
|
|
||||||
});
|
|
||||||
|
|
||||||
const logger = createScopedLogger('Chat');
|
const logger = createScopedLogger('Chat');
|
||||||
|
|
||||||
export function Chat() {
|
export function Chat() {
|
||||||
@@ -56,34 +51,6 @@ export function Chat() {
|
|||||||
importChat={importChat}
|
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',
|
source: 'github',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show success toast notification
|
||||||
|
toast.success(`🚀 GitHub deployment preparation completed successfully!`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
files: fileContents,
|
files: fileContents,
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ export function useGitLabDeploy() {
|
|||||||
source: 'gitlab',
|
source: 'gitlab',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show success toast notification
|
||||||
|
toast.success(`🚀 GitLab deployment preparation completed successfully!`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
files: fileContents,
|
files: fileContents,
|
||||||
|
|||||||
@@ -224,6 +224,9 @@ export function useNetlifyDeploy() {
|
|||||||
source: 'netlify',
|
source: 'netlify',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show success toast notification
|
||||||
|
toast.success(`🚀 Netlify deployment completed successfully!`);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Deploy error:', error);
|
console.error('Deploy error:', error);
|
||||||
|
|||||||
@@ -213,6 +213,9 @@ export function useVercelDeploy() {
|
|||||||
source: 'vercel',
|
source: 'vercel',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show success toast notification
|
||||||
|
toast.success(`🚀 Vercel deployment completed successfully!`);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Vercel deploy error:', err);
|
console.error('Vercel deploy error:', err);
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ const buttonVariants = cva(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
||||||
VariantProps<typeof buttonVariants> {
|
|
||||||
_asChild?: boolean;
|
_asChild?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ export interface StickToBottomContext {
|
|||||||
const StickToBottomContext = createContext<StickToBottomContext | null>(null);
|
const StickToBottomContext = createContext<StickToBottomContext | null>(null);
|
||||||
|
|
||||||
export interface StickToBottomProps
|
export interface StickToBottomProps
|
||||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,
|
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>, StickToBottomOptions {
|
||||||
StickToBottomOptions {
|
|
||||||
contextRef?: React.Ref<StickToBottomContext>;
|
contextRef?: React.Ref<StickToBottomContext>;
|
||||||
instance?: ReturnType<typeof useStickToBottom>;
|
instance?: ReturnType<typeof useStickToBottom>;
|
||||||
children: ((context: StickToBottomContext) => ReactNode) | ReactNode;
|
children: ((context: StickToBottomContext) => ReactNode) | ReactNode;
|
||||||
|
|||||||
@@ -20,43 +20,47 @@ const PREVIEW_CHANNEL = 'preview-updates';
|
|||||||
export class PreviewsStore {
|
export class PreviewsStore {
|
||||||
#availablePreviews = new Map<number, PreviewInfo>();
|
#availablePreviews = new Map<number, PreviewInfo>();
|
||||||
#webcontainer: Promise<WebContainer>;
|
#webcontainer: Promise<WebContainer>;
|
||||||
#broadcastChannel: BroadcastChannel;
|
#broadcastChannel?: BroadcastChannel;
|
||||||
#lastUpdate = new Map<string, number>();
|
#lastUpdate = new Map<string, number>();
|
||||||
#watchedFiles = new Set<string>();
|
#watchedFiles = new Set<string>();
|
||||||
#refreshTimeouts = new Map<string, NodeJS.Timeout>();
|
#refreshTimeouts = new Map<string, NodeJS.Timeout>();
|
||||||
#REFRESH_DELAY = 300;
|
#REFRESH_DELAY = 300;
|
||||||
#storageChannel: BroadcastChannel;
|
#storageChannel?: BroadcastChannel;
|
||||||
|
|
||||||
previews = atom<PreviewInfo[]>([]);
|
previews = atom<PreviewInfo[]>([]);
|
||||||
|
|
||||||
constructor(webcontainerPromise: Promise<WebContainer>) {
|
constructor(webcontainerPromise: Promise<WebContainer>) {
|
||||||
this.#webcontainer = webcontainerPromise;
|
this.#webcontainer = webcontainerPromise;
|
||||||
this.#broadcastChannel = new BroadcastChannel(PREVIEW_CHANNEL);
|
this.#broadcastChannel = this.#maybeCreateChannel(PREVIEW_CHANNEL);
|
||||||
this.#storageChannel = new BroadcastChannel('storage-sync-channel');
|
this.#storageChannel = this.#maybeCreateChannel('storage-sync-channel');
|
||||||
|
|
||||||
// Listen for preview updates from other tabs
|
if (this.#broadcastChannel) {
|
||||||
this.#broadcastChannel.onmessage = (event) => {
|
// Listen for preview updates from other tabs
|
||||||
const { type, previewId } = event.data;
|
this.#broadcastChannel.onmessage = (event) => {
|
||||||
|
const { type, previewId } = event.data;
|
||||||
|
|
||||||
if (type === 'file-change') {
|
if (type === 'file-change') {
|
||||||
const timestamp = event.data.timestamp;
|
const timestamp = event.data.timestamp;
|
||||||
const lastUpdate = this.#lastUpdate.get(previewId) || 0;
|
const lastUpdate = this.#lastUpdate.get(previewId) || 0;
|
||||||
|
|
||||||
if (timestamp > lastUpdate) {
|
if (timestamp > lastUpdate) {
|
||||||
this.#lastUpdate.set(previewId, timestamp);
|
this.#lastUpdate.set(previewId, timestamp);
|
||||||
this.refreshPreview(previewId);
|
this.refreshPreview(previewId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
// Listen for storage sync messages
|
if (this.#storageChannel) {
|
||||||
this.#storageChannel.onmessage = (event) => {
|
// Listen for storage sync messages
|
||||||
const { storage, source } = event.data;
|
this.#storageChannel.onmessage = (event) => {
|
||||||
|
const { storage, source } = event.data;
|
||||||
|
|
||||||
if (storage && source !== this._getTabId()) {
|
if (storage && source !== this._getTabId()) {
|
||||||
this._syncStorage(storage);
|
this._syncStorage(storage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Override localStorage setItem to catch all changes
|
// Override localStorage setItem to catch all changes
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -71,6 +75,29 @@ export class PreviewsStore {
|
|||||||
this.#init();
|
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
|
// Generate a unique ID for this tab
|
||||||
private _getTabId(): string {
|
private _getTabId(): string {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -130,7 +157,7 @@ export class PreviewsStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#storageChannel.postMessage({
|
this.#storageChannel?.postMessage({
|
||||||
type: 'storage-sync',
|
type: 'storage-sync',
|
||||||
storage,
|
storage,
|
||||||
source: this._getTabId(),
|
source: this._getTabId(),
|
||||||
@@ -192,7 +219,7 @@ export class PreviewsStore {
|
|||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
this.#lastUpdate.set(previewId, timestamp);
|
this.#lastUpdate.set(previewId, timestamp);
|
||||||
|
|
||||||
this.#broadcastChannel.postMessage({
|
this.#broadcastChannel?.postMessage({
|
||||||
type: 'state-change',
|
type: 'state-change',
|
||||||
previewId,
|
previewId,
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -204,7 +231,7 @@ export class PreviewsStore {
|
|||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
this.#lastUpdate.set(previewId, timestamp);
|
this.#lastUpdate.set(previewId, timestamp);
|
||||||
|
|
||||||
this.#broadcastChannel.postMessage({
|
this.#broadcastChannel?.postMessage({
|
||||||
type: 'file-change',
|
type: 'file-change',
|
||||||
previewId,
|
previewId,
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -219,7 +246,7 @@ export class PreviewsStore {
|
|||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
this.#lastUpdate.set(previewId, timestamp);
|
this.#lastUpdate.set(previewId, timestamp);
|
||||||
|
|
||||||
this.#broadcastChannel.postMessage({
|
this.#broadcastChannel?.postMessage({
|
||||||
type: 'file-change',
|
type: 'file-change',
|
||||||
previewId,
|
previewId,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|||||||
@@ -52,11 +52,37 @@ export const shortcutsStore = map<Shortcuts>({
|
|||||||
|
|
||||||
// Create a single key for provider settings
|
// Create a single key for provider settings
|
||||||
const PROVIDER_SETTINGS_KEY = '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
|
// Add this helper function at the top of the file
|
||||||
const isBrowser = typeof window !== 'undefined';
|
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 getInitialProviderSettings = (): ProviderSetting => {
|
||||||
const initialSettings: ProviderSetting = {};
|
const initialSettings: ProviderSetting = {};
|
||||||
|
|
||||||
@@ -92,8 +118,84 @@ const getInitialProviderSettings = (): ProviderSetting => {
|
|||||||
return initialSettings;
|
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 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
|
// Create a function to update provider settings that handles both store and persistence
|
||||||
export const updateProviderSettings = (provider: string, settings: ProviderSetting) => {
|
export const updateProviderSettings = (provider: string, settings: ProviderSetting) => {
|
||||||
const currentSettings = providersStore.get();
|
const currentSettings = providersStore.get();
|
||||||
@@ -113,6 +215,37 @@ export const updateProviderSettings = (provider: string, settings: ProviderSetti
|
|||||||
// Save to localStorage
|
// Save to localStorage
|
||||||
const allSettings = providersStore.get();
|
const allSettings = providersStore.get();
|
||||||
localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(allSettings));
|
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);
|
export const isDebugMode = atom(false);
|
||||||
|
|||||||
@@ -51,8 +51,15 @@ export interface SupabaseConnectionState {
|
|||||||
credentials?: SupabaseCredentials;
|
credentials?: SupabaseCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedConnection = typeof localStorage !== 'undefined' ? localStorage.getItem('supabase_connection') : null;
|
const storage =
|
||||||
const savedCredentials = typeof localStorage !== 'undefined' ? localStorage.getItem('supabaseCredentials') : null;
|
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
|
const initialState: SupabaseConnectionState = savedConnection
|
||||||
? JSON.parse(savedConnection)
|
? JSON.parse(savedConnection)
|
||||||
@@ -75,14 +82,14 @@ if (savedCredentials && !initialState.credentials) {
|
|||||||
|
|
||||||
export const supabaseConnection = atom<SupabaseConnectionState>(initialState);
|
export const supabaseConnection = atom<SupabaseConnectionState>(initialState);
|
||||||
|
|
||||||
if (initialState.token && !initialState.stats) {
|
|
||||||
fetchSupabaseStats(initialState.token).catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isConnecting = atom(false);
|
export const isConnecting = atom(false);
|
||||||
export const isFetchingStats = atom(false);
|
export const isFetchingStats = atom(false);
|
||||||
export const isFetchingApiKeys = atom(false);
|
export const isFetchingApiKeys = atom(false);
|
||||||
|
|
||||||
|
if (initialState.token && !initialState.stats) {
|
||||||
|
fetchSupabaseStats(initialState.token).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
export function updateSupabaseConnection(connection: Partial<SupabaseConnectionState>) {
|
export function updateSupabaseConnection(connection: Partial<SupabaseConnectionState>) {
|
||||||
const currentState = supabaseConnection.get();
|
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
|
* Always save the connection state to localStorage to persist across chats
|
||||||
*/
|
*/
|
||||||
if (connection.user || connection.token || connection.selectedProjectId !== undefined || connection.credentials) {
|
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) {
|
if (newState.credentials) {
|
||||||
localStorage.setItem('supabaseCredentials', JSON.stringify(newState.credentials));
|
storage?.setItem('supabaseCredentials', JSON.stringify(newState.credentials));
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('supabaseCredentials');
|
storage?.removeItem('supabaseCredentials');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('supabase_connection');
|
storage?.removeItem('supabase_connection');
|
||||||
localStorage.removeItem('supabaseCredentials');
|
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 { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import { ClientOnly } from 'remix-utils/client-only';
|
import { ClientOnly } from 'remix-utils/client-only';
|
||||||
|
import { cssTransition, ToastContainer } from 'react-toastify';
|
||||||
|
|
||||||
import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
|
import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url';
|
||||||
import globalStyles from './styles/index.scss?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';
|
import 'virtual:uno.css';
|
||||||
|
|
||||||
|
const toastAnimation = cssTransition({
|
||||||
|
enter: 'animated fadeInRight',
|
||||||
|
exit: 'animated fadeOutRight',
|
||||||
|
});
|
||||||
|
|
||||||
export const links: LinksFunction = () => [
|
export const links: LinksFunction = () => [
|
||||||
{
|
{
|
||||||
rel: 'icon',
|
rel: 'icon',
|
||||||
@@ -75,6 +81,31 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ClientOnly>{() => <DndProvider backend={HTML5Backend}>{children}</DndProvider>}</ClientOnly>
|
<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 />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<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 function loader() {
|
||||||
|
return new Response("OK", {
|
||||||
export const loader = async ({ request: _request }: LoaderFunctionArgs) => {
|
status: 200,
|
||||||
return json({
|
headers: {
|
||||||
status: 'healthy',
|
"Content-Type": "text/plain",
|
||||||
timestamp: new Date().toISOString(),
|
},
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -46,17 +46,22 @@ export default function WebContainerPreview() {
|
|||||||
}, [previewId, previewUrl]);
|
}, [previewId, previewUrl]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize broadcast channel
|
const supportsBroadcastChannel = typeof window !== 'undefined' && typeof window.BroadcastChannel === 'function';
|
||||||
broadcastChannelRef.current = new BroadcastChannel(PREVIEW_CHANNEL);
|
|
||||||
|
|
||||||
// Listen for preview updates
|
if (supportsBroadcastChannel) {
|
||||||
broadcastChannelRef.current.onmessage = (event) => {
|
broadcastChannelRef.current = new window.BroadcastChannel(PREVIEW_CHANNEL);
|
||||||
if (event.data.previewId === previewId) {
|
|
||||||
if (event.data.type === 'refresh-preview' || event.data.type === 'file-change') {
|
// Listen for preview updates
|
||||||
handleRefresh();
|
broadcastChannelRef.current.onmessage = (event) => {
|
||||||
|
if (event.data.previewId === previewId) {
|
||||||
|
if (event.data.type === 'refresh-preview' || event.data.type === 'file-change') {
|
||||||
|
handleRefresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
} else {
|
||||||
|
broadcastChannelRef.current = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Construct the WebContainer preview URL
|
// Construct the WebContainer preview URL
|
||||||
const url = `https://${previewId}.local-credentialless.webcontainer-api.io`;
|
const url = `https://${previewId}.local-credentialless.webcontainer-api.io`;
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
@use '../z-index';
|
||||||
|
|
||||||
|
.Toastify__toast-container {
|
||||||
|
@extend .z-toast;
|
||||||
|
}
|
||||||
|
|
||||||
.Toastify__toast {
|
.Toastify__toast {
|
||||||
--at-apply: shadow-md;
|
--at-apply: shadow-md;
|
||||||
|
|
||||||
|
|||||||
@@ -31,3 +31,7 @@ $zIndexMax: 999;
|
|||||||
.z-max {
|
.z-max {
|
||||||
z-index: $zIndexMax;
|
z-index: $zIndexMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.z-toast {
|
||||||
|
z-index: $zIndexMax + 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ services:
|
|||||||
app-dev:
|
app-dev:
|
||||||
image: bolt-ai:development
|
image: bolt-ai:development
|
||||||
build:
|
build:
|
||||||
target: bolt-ai-development
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: development
|
||||||
env_file:
|
env_file:
|
||||||
- '.env'
|
- '.env'
|
||||||
- '.env.local'
|
- '.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",
|
"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",
|
"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: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",
|
"typecheck": "tsc",
|
||||||
"typegen": "wrangler types",
|
"typegen": "wrangler types",
|
||||||
"preview": "pnpm run build && pnpm run start",
|
"preview": "pnpm run build && pnpm run start",
|
||||||
"prepare": "husky",
|
|
||||||
"clean": "node scripts/clean.js",
|
"clean": "node scripts/clean.js",
|
||||||
"electron:dev": "node scripts/electron-dev.mjs",
|
"electron:dev": "node scripts/electron-dev.mjs",
|
||||||
"electron:dev:inspect": "NODE_ENV=development electron --inspect=9229 build/electron/main/index.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: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: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: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": {
|
"engines": {
|
||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@remix-run/cloudflare": "^2.15.2",
|
"@remix-run/cloudflare": "^2.15.2",
|
||||||
"@remix-run/cloudflare-pages": "^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",
|
"@remix-run/react": "^2.15.2",
|
||||||
"@tanstack/react-virtual": "^3.13.0",
|
"@tanstack/react-virtual": "^3.13.0",
|
||||||
"@types/react-beautiful-dnd": "^13.1.8",
|
"@types/react-beautiful-dnd": "^13.1.8",
|
||||||
@@ -157,11 +157,15 @@
|
|||||||
"use-debounce": "^10.0.4",
|
"use-debounce": "^10.0.4",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
"zod": "^3.24.1",
|
"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": {
|
"devDependencies": {
|
||||||
"@blitz/eslint-plugin": "0.1.0",
|
"@blitz/eslint-plugin": "0.1.0",
|
||||||
"@cloudflare/workers-types": "^4.20241127.0",
|
"@cloudflare/workers-types": "^4.20251011.0",
|
||||||
"@electron/notarize": "^2.5.0",
|
"@electron/notarize": "^2.5.0",
|
||||||
"@iconify-json/ph": "^1.2.1",
|
"@iconify-json/ph": "^1.2.1",
|
||||||
"@iconify/types": "^2.0.0",
|
"@iconify/types": "^2.0.0",
|
||||||
@@ -204,7 +208,7 @@
|
|||||||
"vite-plugin-optimize-css-modules": "^1.1.0",
|
"vite-plugin-optimize-css-modules": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^2.1.7",
|
"vitest": "^2.1.7",
|
||||||
"wrangler": "^4.5.1"
|
"wrangler": "^4.44.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@typescript-eslint/utils": "^8.0.0-alpha.30"
|
"@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)
|
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':
|
'@remix-run/cloudflare':
|
||||||
specifier: ^2.15.2
|
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':
|
'@remix-run/cloudflare-pages':
|
||||||
specifier: ^2.15.2
|
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':
|
'@remix-run/node':
|
||||||
specifier: ^2.15.2
|
specifier: ^2.15.2
|
||||||
version: 2.16.8(typescript@5.8.3)
|
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)
|
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:
|
remix-utils:
|
||||||
specifier: ^7.7.0
|
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:
|
rollup-plugin-node-polyfills:
|
||||||
specifier: ^0.2.1
|
specifier: ^0.2.1
|
||||||
version: 0.2.1
|
version: 0.2.1
|
||||||
@@ -352,8 +352,8 @@ importers:
|
|||||||
specifier: 0.1.0
|
specifier: 0.1.0
|
||||||
version: 0.1.0(jiti@1.21.7)(prettier@3.6.2)(typescript@5.8.3)
|
version: 0.1.0(jiti@1.21.7)(prettier@3.6.2)(typescript@5.8.3)
|
||||||
'@cloudflare/workers-types':
|
'@cloudflare/workers-types':
|
||||||
specifier: ^4.20241127.0
|
specifier: ^4.20251011.0
|
||||||
version: 4.20250722.0
|
version: 4.20251014.0
|
||||||
'@electron/notarize':
|
'@electron/notarize':
|
||||||
specifier: ^2.5.0
|
specifier: ^2.5.0
|
||||||
version: 2.5.0
|
version: 2.5.0
|
||||||
@@ -365,7 +365,7 @@ importers:
|
|||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
'@remix-run/dev':
|
'@remix-run/dev':
|
||||||
specifier: ^2.15.2
|
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':
|
'@remix-run/serve':
|
||||||
specifier: ^2.15.2
|
specifier: ^2.15.2
|
||||||
version: 2.16.8(typescript@5.8.3)
|
version: 2.16.8(typescript@5.8.3)
|
||||||
@@ -419,7 +419,7 @@ importers:
|
|||||||
version: 33.4.11
|
version: 33.4.11
|
||||||
electron-builder:
|
electron-builder:
|
||||||
specifier: ^26.0.12
|
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:
|
eslint-config-prettier:
|
||||||
specifier: ^10.1.1
|
specifier: ^10.1.1
|
||||||
version: 10.1.8(eslint@9.31.0(jiti@1.21.7))
|
version: 10.1.8(eslint@9.31.0(jiti@1.21.7))
|
||||||
@@ -481,8 +481,8 @@ importers:
|
|||||||
specifier: ^2.1.7
|
specifier: ^2.1.7
|
||||||
version: 2.1.9(@types/node@24.1.0)(jsdom@26.1.0)(sass-embedded@1.89.2)
|
version: 2.1.9(@types/node@24.1.0)(jsdom@26.1.0)(sass-embedded@1.89.2)
|
||||||
wrangler:
|
wrangler:
|
||||||
specifier: ^4.5.1
|
specifier: ^4.44.0
|
||||||
version: 4.25.1(@cloudflare/workers-types@4.20250722.0)
|
version: 4.44.0(@cloudflare/workers-types@4.20251014.0)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -954,47 +954,47 @@ packages:
|
|||||||
resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==}
|
resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@cloudflare/unenv-preset@2.3.3':
|
'@cloudflare/unenv-preset@2.7.8':
|
||||||
resolution: {integrity: sha512-/M3MEcj3V2WHIRSW1eAQBPRJ6JnGQHc6JKMAPLkDb7pLs3m6X9ES/+K3ceGqxI6TKeF32AWAi7ls0AYzVxCP0A==}
|
resolution: {integrity: sha512-Ky929MfHh+qPhwCapYrRPwPVHtA2Ioex/DbGZyskGyNRDe9Ru3WThYZivyNVaPy5ergQSgMs9OKrM9Ajtz9F6w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
unenv: 2.0.0-rc.17
|
unenv: 2.0.0-rc.21
|
||||||
workerd: ^1.20250508.0
|
workerd: ^1.20250927.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
workerd:
|
workerd:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-64@1.20250712.0':
|
'@cloudflare/workerd-darwin-64@1.20251011.0':
|
||||||
resolution: {integrity: sha512-M6S6a/LQ0Jb0R+g0XhlYi1adGifvYmxA5mD/i9TuZZgjs2bIm5ELuka/n3SCnI98ltvlx3HahRaHagAtOilsFg==}
|
resolution: {integrity: sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-arm64@1.20250712.0':
|
'@cloudflare/workerd-darwin-arm64@1.20251011.0':
|
||||||
resolution: {integrity: sha512-7sFzn6rvAcnLy7MktFL42dYtzL0Idw/kiUmNf2P3TvsBRoShhLK5ZKhbw+NAhvU8e4pXWm5lkE0XmpieA0zNjw==}
|
resolution: {integrity: sha512-1WuFBGwZd15p4xssGN/48OE2oqokIuc51YvHvyNivyV8IYnAs3G9bJNGWth1X7iMDPe4g44pZrKhRnISS2+5dA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-64@1.20250712.0':
|
'@cloudflare/workerd-linux-64@1.20251011.0':
|
||||||
resolution: {integrity: sha512-EFRrGe/bqK7NHtht7vNlbrDpfvH3eRvtJOgsTpEQEysDjVmlK6pVJxSnLy9Hg1zlLY15IfhfGC+K2qisseHGJQ==}
|
resolution: {integrity: sha512-BccMiBzFlWZyFghIw2szanmYJrJGBGHomw2y/GV6pYXChFzMGZkeCEMfmCyJj29xczZXxcZmUVJxNy4eJxO8QA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-arm64@1.20250712.0':
|
'@cloudflare/workerd-linux-arm64@1.20251011.0':
|
||||||
resolution: {integrity: sha512-rG8JUleddhUHQVwpXOYv0VbL0S9kOtR9PNKecgVhFpxEhC8aTeg2HNBBjo8st7IfcUvY8WaW3pD3qdAMZ05UwQ==}
|
resolution: {integrity: sha512-79o/216lsbAbKEVDZYXR24ivEIE2ysDL9jvo0rDTkViLWju9dAp3CpyetglpJatbSi3uWBPKZBEOqN68zIjVsQ==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@cloudflare/workerd-windows-64@1.20250712.0':
|
'@cloudflare/workerd-windows-64@1.20251011.0':
|
||||||
resolution: {integrity: sha512-qS8H5RCYwE21Om9wo5/F807ClBJIfknhuLBj16eYxvJcj9JqgAKWi12BGgjyGxHuJJjeoQ63lr4wHAdbFntDDg==}
|
resolution: {integrity: sha512-RIXUQRchFdqEvaUqn1cXZXSKjpqMaSaVAkI5jNZ8XzAw/bw2bcdOVUtakrflgxDprltjFb0PTNtuss1FKtH9Jg==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@cloudflare/workers-types@4.20250722.0':
|
'@cloudflare/workers-types@4.20251014.0':
|
||||||
resolution: {integrity: sha512-pTY+A07DTSacgUBYcVEEb78/KG7THdcRpPqXLeH/A/LHHobAddgN4zyXBldsoZuzy7bD9tZYJW+wkcyR4k7fDA==}
|
resolution: {integrity: sha512-tEW98J/kOa0TdylIUOrLKRdwkUw0rvvYVlo+Ce0mqRH3c8kSoxLzUH9gfCvwLe0M89z1RkzFovSKAW2Nwtyn3w==}
|
||||||
|
|
||||||
'@codemirror/autocomplete@6.18.6':
|
'@codemirror/autocomplete@6.18.6':
|
||||||
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
|
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
|
||||||
@@ -6068,8 +6068,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
miniflare@4.20250712.1:
|
miniflare@4.20251011.0:
|
||||||
resolution: {integrity: sha512-46gB3FGPOsy+EpFGufjhr8agYycO/55d6l0y7hNJ13NcTVwrObMg/0HmI3pC5yQj0974IVXzBgUfDBMAX6thow==}
|
resolution: {integrity: sha512-DlZ7vR5q/RE9eLsxsrXzfSZIF2f6O5k0YsFrSKhWUtdefyGtJt4sSpR6V+Af/waaZ6+zIFy9lsknHBCm49sEYA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -7765,12 +7765,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==}
|
resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==}
|
||||||
engines: {node: '>=18.17'}
|
engines: {node: '>=18.17'}
|
||||||
|
|
||||||
undici@7.12.0:
|
undici@7.14.0:
|
||||||
resolution: {integrity: sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==}
|
resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==}
|
||||||
engines: {node: '>=20.18.1'}
|
engines: {node: '>=20.18.1'}
|
||||||
|
|
||||||
unenv@2.0.0-rc.17:
|
unenv@2.0.0-rc.21:
|
||||||
resolution: {integrity: sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg==}
|
resolution: {integrity: sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==}
|
||||||
|
|
||||||
unified@10.1.2:
|
unified@10.1.2:
|
||||||
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
|
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
|
||||||
@@ -8137,17 +8137,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
workerd@1.20250712.0:
|
workerd@1.20251011.0:
|
||||||
resolution: {integrity: sha512-7h+k1OxREpiZW0849g0uQNexRWMcs5i5gUGhJzCY8nIx6Tv4D/ndlXJ47lEFj7/LQdp165IL9dM2D5uDiedZrg==}
|
resolution: {integrity: sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
wrangler@4.25.1:
|
wrangler@4.44.0:
|
||||||
resolution: {integrity: sha512-4Tlg+jmqxCX3xFm+Nz1b4jHHY9iOu1EyJ17SSCCJ6MGp+FCGtXgr+CynT94+MP0v/qKQUkMKjoeJ5FNDunZ9cA==}
|
resolution: {integrity: sha512-BLOUigckcWZ0r4rm7b5PuaTpb9KP9as0XeCRSJ8kqcNgXcKoUD3Ij8FlPvN25KybLnFnetaO0ZdfRYUPWle4qw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@cloudflare/workers-types': ^4.20250712.0
|
'@cloudflare/workers-types': ^4.20251011.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@cloudflare/workers-types':
|
'@cloudflare/workers-types':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -9104,28 +9104,28 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime: 3.0.0
|
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:
|
dependencies:
|
||||||
unenv: 2.0.0-rc.17
|
unenv: 2.0.0-rc.21
|
||||||
optionalDependencies:
|
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
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-darwin-arm64@1.20250712.0':
|
'@cloudflare/workerd-darwin-arm64@1.20251011.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-64@1.20250712.0':
|
'@cloudflare/workerd-linux-64@1.20251011.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-linux-arm64@1.20250712.0':
|
'@cloudflare/workerd-linux-arm64@1.20251011.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workerd-windows-64@1.20250712.0':
|
'@cloudflare/workerd-windows-64@1.20251011.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@cloudflare/workers-types@4.20250722.0': {}
|
'@cloudflare/workers-types@4.20251014.0': {}
|
||||||
|
|
||||||
'@codemirror/autocomplete@6.18.6':
|
'@codemirror/autocomplete@6.18.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10765,22 +10765,22 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
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:
|
dependencies:
|
||||||
'@cloudflare/workers-types': 4.20250722.0
|
'@cloudflare/workers-types': 4.20251014.0
|
||||||
'@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)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
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:
|
dependencies:
|
||||||
'@cloudflare/kv-asset-handler': 0.1.3
|
'@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)
|
'@remix-run/server-runtime': 2.16.8(typescript@5.8.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
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:
|
dependencies:
|
||||||
'@babel/core': 7.28.0
|
'@babel/core': 7.28.0
|
||||||
'@babel/generator': 7.28.0
|
'@babel/generator': 7.28.0
|
||||||
@@ -10843,7 +10843,7 @@ snapshots:
|
|||||||
'@remix-run/serve': 2.16.8(typescript@5.8.3)
|
'@remix-run/serve': 2.16.8(typescript@5.8.3)
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
vite: 5.4.19(@types/node@24.1.0)(sass-embedded@1.89.2)
|
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:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@@ -12074,7 +12074,7 @@ snapshots:
|
|||||||
|
|
||||||
app-builder-bin@5.0.0-alpha.12: {}
|
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:
|
dependencies:
|
||||||
'@develar/schema-utils': 2.6.5
|
'@develar/schema-utils': 2.6.5
|
||||||
'@electron/asar': 3.2.18
|
'@electron/asar': 3.2.18
|
||||||
@@ -12887,7 +12887,7 @@ snapshots:
|
|||||||
|
|
||||||
dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12):
|
dmg-builder@26.0.12(electron-builder-squirrel-windows@26.0.12):
|
||||||
dependencies:
|
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: 26.0.11
|
||||||
builder-util-runtime: 9.3.1
|
builder-util-runtime: 9.3.1
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
@@ -12966,7 +12966,7 @@ snapshots:
|
|||||||
|
|
||||||
electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12):
|
electron-builder-squirrel-windows@26.0.12(dmg-builder@26.0.12):
|
||||||
dependencies:
|
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: 26.0.11
|
||||||
electron-winstaller: 5.4.0
|
electron-winstaller: 5.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -12974,9 +12974,9 @@ snapshots:
|
|||||||
- dmg-builder
|
- dmg-builder
|
||||||
- supports-color
|
- 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:
|
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: 26.0.11
|
||||||
builder-util-runtime: 9.3.1
|
builder-util-runtime: 9.3.1
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
@@ -15269,7 +15269,7 @@ snapshots:
|
|||||||
|
|
||||||
min-indent@1.0.1: {}
|
min-indent@1.0.1: {}
|
||||||
|
|
||||||
miniflare@4.20250712.1:
|
miniflare@4.20251011.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cspotcode/source-map-support': 0.8.1
|
'@cspotcode/source-map-support': 0.8.1
|
||||||
acorn: 8.14.0
|
acorn: 8.14.0
|
||||||
@@ -15278,8 +15278,8 @@ snapshots:
|
|||||||
glob-to-regexp: 0.4.1
|
glob-to-regexp: 0.4.1
|
||||||
sharp: 0.33.5
|
sharp: 0.33.5
|
||||||
stoppable: 1.1.0
|
stoppable: 1.1.0
|
||||||
undici: 7.12.0
|
undici: 7.14.0
|
||||||
workerd: 1.20250712.0
|
workerd: 1.20251011.0
|
||||||
ws: 8.18.0
|
ws: 8.18.0
|
||||||
youch: 4.1.0-beta.10
|
youch: 4.1.0-beta.10
|
||||||
zod: 3.22.3
|
zod: 3.22.3
|
||||||
@@ -16229,11 +16229,11 @@ snapshots:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(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:
|
dependencies:
|
||||||
type-fest: 4.41.0
|
type-fest: 4.41.0
|
||||||
optionalDependencies:
|
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/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/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
|
'@remix-run/router': 1.23.0
|
||||||
@@ -17072,9 +17072,9 @@ snapshots:
|
|||||||
|
|
||||||
undici@6.21.3: {}
|
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:
|
dependencies:
|
||||||
defu: 6.1.4
|
defu: 6.1.4
|
||||||
exsolve: 1.0.7
|
exsolve: 1.0.7
|
||||||
@@ -17522,26 +17522,26 @@ snapshots:
|
|||||||
|
|
||||||
word-wrap@1.2.5: {}
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
workerd@1.20250712.0:
|
workerd@1.20251011.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@cloudflare/workerd-darwin-64': 1.20250712.0
|
'@cloudflare/workerd-darwin-64': 1.20251011.0
|
||||||
'@cloudflare/workerd-darwin-arm64': 1.20250712.0
|
'@cloudflare/workerd-darwin-arm64': 1.20251011.0
|
||||||
'@cloudflare/workerd-linux-64': 1.20250712.0
|
'@cloudflare/workerd-linux-64': 1.20251011.0
|
||||||
'@cloudflare/workerd-linux-arm64': 1.20250712.0
|
'@cloudflare/workerd-linux-arm64': 1.20251011.0
|
||||||
'@cloudflare/workerd-windows-64': 1.20250712.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:
|
dependencies:
|
||||||
'@cloudflare/kv-asset-handler': 0.4.0
|
'@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
|
blake3-wasm: 2.1.5
|
||||||
esbuild: 0.25.4
|
esbuild: 0.25.4
|
||||||
miniflare: 4.20250712.1
|
miniflare: 4.20251011.0
|
||||||
path-to-regexp: 6.3.0
|
path-to-regexp: 6.3.0
|
||||||
unenv: 2.0.0-rc.17
|
unenv: 2.0.0-rc.21
|
||||||
workerd: 1.20250712.0
|
workerd: 1.20251011.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@cloudflare/workers-types': 4.20250722.0
|
'@cloudflare/workers-types': 4.20251014.0
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- 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