diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..dd9fac725f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +# Enforce LF line endings for all text files +* text=auto eol=lf + +# Explicitly mark as text +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.json text eol=lf +*.md text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.html text eol=lf +*.svg text eol=lf +*.yml text eol=lf +*.yaml text eol=lf + +# Binary files +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.woff binary +*.woff2 binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 83e293df76..8d706a57b5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -6,8 +6,8 @@ body: value: | Thank you for reporting an issue :pray:. - This issue tracker is for bugs and issues found with [Bolt.new](https://bolt.new). - If you experience issues related to WebContainer, please file an issue in our [WebContainer repo](https://github.com/stackblitz/webcontainer-core), or file an issue in our [StackBlitz core repo](https://github.com/stackblitz/core) for issues with StackBlitz. + This issue tracker is for bugs and issues found with X Builder. + For issues related to the upstream Bolt.new codebase, see [stackblitz/bolt.new](https://github.com/stackblitz/bolt.new). The more information you fill in, the better we can help you. - type: textarea @@ -20,7 +20,7 @@ body: - type: input id: link attributes: - label: Link to the Bolt URL that caused the error + label: Link to the X Builder URL that caused the error description: Please do not delete it after reporting! validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e744c79187..316b41c037 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,5 @@ blank_issues_enabled: false contact_links: - - name: Bolt.new Help Center - url: https://support.bolt.new - about: Official central repository for tips, tricks, tutorials, known issues, and best practices for bolt.new usage. - - name: Billing Issues - url: https://support.bolt.new/Billing-13fd971055d680ebb393cb80973710b6 - about: Instructions for billing and subscription related support - - name: Discord Chat - url: https://discord.gg/stackblitz - about: Build, share, and learn with other Bolters in real time. + - name: Upstream Project (Bolt.new) + url: https://github.com/stackblitz/bolt.new + about: Original Bolt.new project by StackBlitz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..40b27f10c4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + ci: + name: Lint, Typecheck & Test + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.4.0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.15.1' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm run lint + + - name: Typecheck + run: pnpm run typecheck + + - name: Test + run: pnpm test diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 0000000000..3830125698 --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,45 @@ +name: Deploy Staging + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + deploy: + name: Deploy to Cloudflare Pages + runs-on: ubuntu-latest + # Only deploy after CI passes + needs: [] + + permissions: + contents: read + deployments: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.4.0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.15.1' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm run build + + - name: Deploy to Cloudflare Pages + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy ./build/client --project-name=x-builder-staging diff --git a/README.md b/README.md index d3745298ff..ce3c0307b7 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,119 @@ -[![Bolt.new: AI-Powered Full-Stack Web Development in the Browser](./public/social_preview_index.jpg)](https://bolt.new) +# X Builder -# Bolt.new: AI-Powered Full-Stack Web Development in the Browser +[![CI](https://github.com/yosiwizman/x-builder/actions/workflows/ci.yml/badge.svg)](https://github.com/yosiwizman/x-builder/actions/workflows/ci.yml) -Bolt.new is an AI-powered web development agent that allows you to prompt, run, edit, and deploy full-stack applications directly from your browser—no local setup required. If you're here to build your own AI-powered web dev agent using the Bolt open source codebase, [click here to get started!](./CONTRIBUTING.md) +AI-powered full-stack web development in the browser. -## What Makes Bolt.new Different +> **Based on [Bolt.new](https://github.com/stackblitz/bolt.new)** - the open-source AI web development agent by StackBlitz. -Claude, v0, etc are incredible- but you can't install packages, run backends or edit code. That’s where Bolt.new stands out: +## Production -- **Full-Stack in the Browser**: Bolt.new integrates cutting-edge AI models with an in-browser development environment powered by **StackBlitz’s WebContainers**. This allows you to: - - Install and run npm tools and libraries (like Vite, Next.js, and more) +🚀 **Live URL**: https://x-builder-staging.pages.dev + +**Status**: Production (Cloudflare Pages) + +> **Note**: Custom domain can be added later without downtime. + +## About + +X Builder is a white-label fork of Bolt.new that allows you to prompt, run, edit, and deploy full-stack applications directly from your browser. + +### Features + +- **Full-Stack in the Browser**: Integrates AI models with an in-browser development environment powered by **StackBlitz's WebContainers** + - Install and run npm tools and libraries (Vite, Next.js, etc.) - Run Node.js servers - Interact with third-party APIs - Deploy to production from chat - - Share your work via a URL -- **AI with Environment Control**: Unlike traditional dev environments where the AI can only assist in code generation, Bolt.new gives AI models **complete control** over the entire environment including the filesystem, node server, package manager, terminal, and browser console. This empowers AI agents to handle the entire app lifecycle—from creation to deployment. +- **AI with Environment Control**: AI models have complete control over the filesystem, node server, package manager, terminal, and browser console -Whether you’re an experienced developer, a PM or designer, Bolt.new allows you to build production-grade full-stack applications with ease. +## Tips and Tricks -For developers interested in building their own AI-powered development tools with WebContainers, check out the open-source Bolt codebase in this repo! +- **Be specific about your stack**: Mention frameworks/libraries in your initial prompt +- **Use the enhance prompt icon**: Refine your prompt with AI assistance before submitting +- **Scaffold basics first**: Establish the foundation before adding advanced features +- **Batch simple instructions**: Combine multiple simple tasks in one message -## Tips and Tricks +## Technical Notes + +### MVP Publish (Cloudflare Pages) + +X Builder includes an MVP publish feature that deploys projects directly to Cloudflare Pages. + +**Components**: +- `app/lib/stores/publish.ts` - State management for publish status +- `app/routes/api.publish.ts` - API endpoint for Cloudflare Pages deployment +- `app/components/workbench/PublishButton.client.tsx` - UI button component + +**Environment Variables** (for publish to work at runtime): +- `CLOUDFLARE_API_TOKEN` - API token with Pages permissions +- `CLOUDFLARE_ACCOUNT_ID` - Your Cloudflare account ID + +> **TODO**: The Cloudflare Pages Direct Upload API implementation may need adjustment +> based on actual API requirements for production use. + +### Cross-Origin Isolation + +X Builder requires `crossOriginIsolated` to be enabled for WebContainers (SharedArrayBuffer). This is achieved via HTTP headers: + +- `Cross-Origin-Opener-Policy: same-origin` +- `Cross-Origin-Embedder-Policy: credentialless` + +These headers are set in: +- `public/_headers` - Cloudflare Pages static headers +- `app/entry.server.tsx` - Server-side rendering +- `functions/[[path]].ts` - Cloudflare Pages Functions +- `vite.config.ts` - Development server + +To verify: Open DevTools console and check `self.crossOriginIsolated === true` + +## Development + +### Prerequisites + +- Node.js 20.15.1+ +- pnpm 9.4.0+ -Here are some tips to get the most out of Bolt.new: +### Setup -- **Be specific about your stack**: If you want to use specific frameworks or libraries (like Astro, Tailwind, ShadCN, or any other popular JavaScript framework), mention them in your initial prompt to ensure Bolt scaffolds the project accordingly. +```bash +pnpm install +pnpm run dev +``` -- **Use the enhance prompt icon**: Before sending your prompt, try clicking the 'enhance' icon to have the AI model help you refine your prompt, then edit the results before submitting. +### Scripts -- **Scaffold the basics first, then add features**: Make sure the basic structure of your application is in place before diving into more advanced functionality. This helps Bolt understand the foundation of your project and ensure everything is wired up right before building out more advanced functionality. +- `pnpm run dev` - Start development server +- `pnpm run build` - Build for production +- `pnpm run lint` - Run ESLint +- `pnpm run typecheck` - Run TypeScript checks +- `pnpm test` - Run tests -- **Batch simple instructions**: Save time by combining simple instructions into one message. For example, you can ask Bolt to change the color scheme, add mobile responsiveness, and restart the dev server, all in one go saving you time and reducing API credit consumption significantly. +### Deployment -## FAQs +Production deploys automatically from `main` branch via GitHub Actions to Cloudflare Pages. -**Where do I sign up for a paid plan?** -Bolt.new is free to get started. If you need more AI tokens or want private projects, you can purchase a paid subscription in your [Bolt.new](https://bolt.new) settings, in the lower-left hand corner of the application. +Required GitHub Secrets: +- `CLOUDFLARE_API_TOKEN` - Cloudflare API token with Pages edit permissions +- `CLOUDFLARE_ACCOUNT_ID` - Your Cloudflare account ID -**What happens if I hit the free usage limit?** -Once your free daily token limit is reached, AI interactions are paused until the next day or until you upgrade your plan. +### Release Process -**Is Bolt in beta?** -Yes, Bolt.new is in beta, and we are actively improving it based on feedback. +``` +1. Create a Pull Request with your changes +2. CI runs automatically (lint, typecheck, tests) +3. CI must pass before merge is allowed +4. Merge PR to main +5. Auto-deploy to production (Cloudflare Pages) +``` -**How can I report Bolt.new issues?** -Check out the [Issues section](https://github.com/stackblitz/bolt.new/issues) to report an issue or request a new feature. Please use the search feature to check if someone else has already submitted the same issue/request. +**Safeguards**: +- Branch protection requires PR reviews +- All CI checks must pass +- Direct pushes to `main` are blocked +- Linear history enforced -**What frameworks/libraries currently work on Bolt?** -Bolt.new supports most popular JavaScript frameworks and libraries. If it runs on StackBlitz, it will run on Bolt.new as well. +## Attribution -**How can I add make sure my framework/project works well in bolt?** -We are excited to work with the JavaScript ecosystem to improve functionality in Bolt. Reach out to us via [hello@stackblitz.com](mailto:hello@stackblitz.com) to discuss how we can partner! +This project is based on [Bolt.new](https://github.com/stackblitz/bolt.new) by [StackBlitz](https://stackblitz.com/), licensed under MIT. diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index c4f90f43a1..e6dea3a8a2 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -130,7 +130,7 @@ export const BaseChat = React.forwardRef( minHeight: TEXTAREA_MIN_HEIGHT, maxHeight: TEXTAREA_MAX_HEIGHT, }} - placeholder="How can Bolt help you today?" + placeholder="How can X Builder help you today?" translate="no" /> diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx index 15cf4bfbd0..dd3034a3cc 100644 --- a/app/components/header/Header.tsx +++ b/app/components/header/Header.tsx @@ -21,7 +21,7 @@ export function Header() {
- + X Builder
diff --git a/app/components/sidebar/Menu.client.tsx b/app/components/sidebar/Menu.client.tsx index cf6d97812c..e99d5bb4ef 100644 --- a/app/components/sidebar/Menu.client.tsx +++ b/app/components/sidebar/Menu.client.tsx @@ -2,7 +2,6 @@ import { motion, type Variants } from 'framer-motion'; import { useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'react-toastify'; import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; -import { IconButton } from '~/components/ui/IconButton'; import { ThemeSwitch } from '~/components/ui/ThemeSwitch'; import { db, deleteById, getAll, chatId, type ChatHistoryItem } from '~/lib/persistence'; import { cubicEasingFn } from '~/utils/easings'; diff --git a/app/components/workbench/PublishButton.client.tsx b/app/components/workbench/PublishButton.client.tsx new file mode 100644 index 0000000000..8b42c0c34e --- /dev/null +++ b/app/components/workbench/PublishButton.client.tsx @@ -0,0 +1,120 @@ +import { useStore } from '@nanostores/react'; +import { memo, useCallback } from 'react'; +import { toast } from 'react-toastify'; +import { PanelHeaderButton } from '~/components/ui/PanelHeaderButton'; +import { + publishState, + setPublishStatus, + setPublishSuccess, + setPublishError, + resetPublishState, +} from '~/lib/stores/publish'; +import { workbenchStore } from '~/lib/stores/workbench'; + +interface PublishButtonProps { + className?: string; +} + +export const PublishButton = memo(({ className }: PublishButtonProps) => { + const { status, url } = useStore(publishState); + const isPublishing = status === 'publishing'; + + const handlePublish = useCallback(async () => { + const files = workbenchStore.files.get(); + + if (!files || Object.keys(files).length === 0) { + toast.error('No files to publish'); + return; + } + + // convert files to publishable format (only file contents, not directories) + const publishableFiles: Record = {}; + + for (const [path, dirent] of Object.entries(files)) { + if (dirent?.type === 'file' && dirent.content) { + publishableFiles[path] = dirent.content; + } + } + + if (Object.keys(publishableFiles).length === 0) { + toast.error('No file content to publish'); + return; + } + + setPublishStatus('publishing'); + toast.info('Publishing project...'); + + try { + const response = await fetch('/api/publish', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ files: publishableFiles }), + }); + + const data = (await response.json()) as { url?: string; error?: string; success?: boolean }; + + if (!response.ok) { + throw new Error(data.error || 'Publish failed'); + } + + const publishUrl = data.url || ''; + setPublishSuccess(publishUrl); + toast.success( +
+ Published successfully! +
+ + {publishUrl} + +
, + { autoClose: false }, + ); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + setPublishError(message); + toast.error(`Publish failed: ${message}`); + } + }, []); + + const handleViewDeployment = useCallback(() => { + if (url) { + window.open(url, '_blank', 'noopener,noreferrer'); + } + }, [url]); + + const handleReset = useCallback(() => { + resetPublishState(); + }, []); + + if (status === 'success' && url) { + return ( +
+ +
+ View Site + + +
+ +
+ ); + } + + return ( + + {isPublishing ? ( + <> +
+ Publishing... + + ) : ( + <> +
+ Publish + + )} + + ); +}); diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 4baf07001d..3503a52217 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -68,8 +68,11 @@ export default async function handleRequest( responseHeaders.set('Content-Type', 'text/html'); - responseHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp'); + /** + * Enable crossOriginIsolated for SharedArrayBuffer (required by WebContainers). + */ responseHeaders.set('Cross-Origin-Opener-Policy', 'same-origin'); + responseHeaders.set('Cross-Origin-Embedder-Policy', 'credentialless'); return new Response(body, { headers: responseHeaders, diff --git a/app/lib/.server/llm/prompts.ts b/app/lib/.server/llm/prompts.ts index f78b418731..4fbe6c08c0 100644 --- a/app/lib/.server/llm/prompts.ts +++ b/app/lib/.server/llm/prompts.ts @@ -3,7 +3,7 @@ import { allowedHTMLElements } from '~/utils/markdown'; import { stripIndents } from '~/utils/stripIndent'; export const getSystemPrompt = (cwd: string = WORK_DIR) => ` -You are Bolt, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices. +You are X Builder, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices. You are operating in an environment called WebContainer, an in-browser Node.js runtime that emulates a Linux system to some degree. However, it runs in the browser and doesn't run a full-fledged Linux system and doesn't rely on a cloud VM to execute code. All code is executed in the browser. It does come with a shell that emulates zsh. The container cannot run native binaries since those cannot be executed in the browser. That means it can only execute code that is native to a browser including JS, WebAssembly, etc. diff --git a/app/lib/stores/publish.ts b/app/lib/stores/publish.ts new file mode 100644 index 0000000000..8140a3fb1d --- /dev/null +++ b/app/lib/stores/publish.ts @@ -0,0 +1,46 @@ +import { atom } from 'nanostores'; + +export type PublishStatus = 'idle' | 'publishing' | 'success' | 'error'; + +export interface PublishState { + status: PublishStatus; + url: string | null; + error: string | null; +} + +export const publishState = atom({ + status: 'idle', + url: null, + error: null, +}); + +export function setPublishStatus(status: PublishStatus) { + publishState.set({ + ...publishState.get(), + status, + }); +} + +export function setPublishSuccess(url: string) { + publishState.set({ + status: 'success', + url, + error: null, + }); +} + +export function setPublishError(error: string) { + publishState.set({ + status: 'error', + url: null, + error, + }); +} + +export function resetPublishState() { + publishState.set({ + status: 'idle', + url: null, + error: null, + }); +} diff --git a/app/root.tsx b/app/root.tsx index 31eb387e03..af45ff3acc 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -69,6 +69,18 @@ export function Layout({ children }: { children: React.ReactNode }) { document.querySelector('html')?.setAttribute('data-theme', theme); }, [theme]); + /** + * Verify crossOriginIsolated is enabled (required for SharedArrayBuffer/WebContainers). + */ + useEffect(() => { + if (typeof window !== 'undefined' && !window.crossOriginIsolated) { + console.warn( + 'crossOriginIsolated is not enabled. SharedArrayBuffer may not work. ' + + 'Ensure COOP/COEP headers are set correctly.', + ); + } + }, []); + return ( <> {children} diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 86d73409c9..944c4b02d6 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -5,7 +5,7 @@ import { Chat } from '~/components/chat/Chat.client'; import { Header } from '~/components/header/Header'; export const meta: MetaFunction = () => { - return [{ title: 'Bolt' }, { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }]; + return [{ title: 'X Builder' }, { name: 'description', content: 'AI-powered full-stack web development' }]; }; export const loader = () => json({}); diff --git a/app/routes/api.publish.ts b/app/routes/api.publish.ts new file mode 100644 index 0000000000..0b61b2eb64 --- /dev/null +++ b/app/routes/api.publish.ts @@ -0,0 +1,149 @@ +import { type ActionFunctionArgs, json } from '@remix-run/cloudflare'; + +interface PublishRequest { + files: Record; + projectName?: string; +} + +interface CloudflareEnv { + CLOUDFLARE_API_TOKEN?: string; + CLOUDFLARE_ACCOUNT_ID?: string; +} + +/** + * MVP Publish API endpoint. + * + * Publishes project files to Cloudflare Pages via Direct Upload API. + * + * NOTE: The Cloudflare Pages Direct Upload API uses FormData with: + * - A "manifest" field containing JSON object mapping file paths to empty strings + * - Individual file fields where field name is the file path and value is file content + */ +export async function action({ context, request }: ActionFunctionArgs) { + const env = context.cloudflare.env as CloudflareEnv; + + const apiToken = env.CLOUDFLARE_API_TOKEN; + const accountId = env.CLOUDFLARE_ACCOUNT_ID; + + if (!apiToken || !accountId) { + return json( + { error: 'Cloudflare credentials not configured. Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.' }, + { status: 500 }, + ); + } + + try { + const { files, projectName = 'x-builder-preview' } = await request.json(); + + if (!files || Object.keys(files).length === 0) { + return json({ error: 'No files provided for publishing' }, { status: 400 }); + } + + /** + * Create a deployment using Cloudflare Pages Direct Upload. + * + * Step 1: Create the project if it doesn't exist. + */ + const projectResponse = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects`, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: projectName, + production_branch: 'main', + }), + }); + + // project might already exist (409), which is fine + if (!projectResponse.ok && projectResponse.status !== 409) { + const errorData = await projectResponse.json(); + console.error('Failed to create project:', errorData); + } + + /** + * Step 2: Create a deployment with direct upload. + * + * Cloudflare Pages Direct Upload expects: + * - manifest: JSON object with file paths as keys, empty strings as values + * - individual files: FormData parts with file path as field name + */ + const formData = new FormData(); + + // build manifest with empty string values (per CF API spec) + const manifest: Record = {}; + + for (const [filePath, content] of Object.entries(files)) { + // normalize path to include leading slash (required by CF Pages) + const normalizedPath = filePath.startsWith('/') ? filePath : `/${filePath}`; + manifest[normalizedPath] = ''; + + // add file content - use path as field name + const contentType = getContentType(normalizedPath); + formData.append(normalizedPath, new Blob([content], { type: contentType }), normalizedPath); + } + + formData.append('manifest', JSON.stringify(manifest)); + + const deployResponse = await fetch( + `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/deployments`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${apiToken}`, + }, + body: formData, + }, + ); + + if (!deployResponse.ok) { + const errorData = await deployResponse.json(); + console.error('Deployment failed:', errorData); + + return json({ error: 'Deployment failed. Check server logs for details.' }, { status: deployResponse.status }); + } + + const deployResult = (await deployResponse.json()) as { + result?: { url?: string; id?: string }; + }; + const deploymentUrl = deployResult.result?.url || `https://${projectName}.pages.dev`; + + return json({ + success: true, + url: deploymentUrl, + deploymentId: deployResult.result?.id, + }); + } catch (error) { + console.error('Publish error:', error); + return json({ error: 'Internal server error during publish' }, { status: 500 }); + } +} + +/** + * Get content type based on file extension. + */ +function getContentType(filePath: string): string { + const ext = filePath.split('.').pop()?.toLowerCase() || ''; + const mimeTypes: Record = { + html: 'text/html', + htm: 'text/html', + css: 'text/css', + js: 'application/javascript', + mjs: 'application/javascript', + json: 'application/json', + png: 'image/png', + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + gif: 'image/gif', + svg: 'image/svg+xml', + ico: 'image/x-icon', + woff: 'font/woff', + woff2: 'font/woff2', + ttf: 'font/ttf', + txt: 'text/plain', + xml: 'application/xml', + }; + + return mimeTypes[ext] || 'application/octet-stream'; +} diff --git a/functions/[[path]].ts b/functions/[[path]].ts index 4f196604d2..9b9f5c0c58 100644 --- a/functions/[[path]].ts +++ b/functions/[[path]].ts @@ -4,6 +4,20 @@ import { createPagesFunctionHandler } from '@remix-run/cloudflare-pages'; // @ts-ignore because the server build file is generated by `remix vite:build` import * as serverBuild from '../build/server'; -export const onRequest = createPagesFunctionHandler({ +const handler = createPagesFunctionHandler({ build: serverBuild as unknown as ServerBuild, }); + +/** + * Wrap handler to add COOP/COEP headers for crossOriginIsolated. + */ +export const onRequest: PagesFunction = async (context) => { + const response = await handler(context); + + // clone response to modify headers + const newResponse = new Response(response.body, response); + newResponse.headers.set('Cross-Origin-Opener-Policy', 'same-origin'); + newResponse.headers.set('Cross-Origin-Embedder-Policy', 'credentialless'); + + return newResponse; +}; diff --git a/icons/logo.svg b/icons/logo.svg index c68d62fd45..4262db8672 100644 --- a/icons/logo.svg +++ b/icons/logo.svg @@ -1,4 +1,4 @@ - - + + X diff --git a/package.json b/package.json index 5583455603..82538a94fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "bolt", - "description": "StackBlitz AI Agent", + "name": "x-builder", + "description": "AI-powered full-stack web development", "private": true, "license": "MIT", "packageManager": "pnpm@9.4.0", @@ -17,7 +17,8 @@ "start": "bindings=$(./bindings.sh) && wrangler pages dev ./build/client $bindings", "typecheck": "tsc", "typegen": "wrangler types", - "preview": "pnpm run build && pnpm run start" + "preview": "pnpm run build && pnpm run start", + "smoke:publish": "tsx scripts/smoke-test-publish.ts" }, "engines": { "node": ">=18.18.0" diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000000..3a270de9d8 --- /dev/null +++ b/public/_headers @@ -0,0 +1,3 @@ +/* + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: credentialless diff --git a/public/favicon.svg b/public/favicon.svg index c68d62fd45..4262db8672 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,4 +1,4 @@ - - + + X diff --git a/vite.config.ts b/vite.config.ts index 58e76cde5d..2c62ac745f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,6 +10,12 @@ export default defineConfig((config) => { build: { target: 'esnext', }, + server: { + headers: { + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'credentialless', + }, + }, plugins: [ nodePolyfills({ include: ['path', 'buffer'], diff --git a/wrangler.toml b/wrangler.toml index 09f2e3a88a..0deda9f1f7 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -1,5 +1,5 @@ #:schema node_modules/wrangler/config-schema.json -name = "bolt" +name = "x-builder" compatibility_flags = ["nodejs_compat"] compatibility_date = "2024-07-01" pages_build_output_dir = "./build/client"