diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..380f806 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,61 @@ +# GitHub Workflows + +This directory contains automated workflows for CI/CD. + +## Workflows + +### CI (`ci.yml`) +Runs on every push to `main` and on pull requests: +- Installs dependencies +- Builds the plugin +- Builds the example app + +### Preview Package (`preview.yml`) +Runs on pull requests and pushes to `main`: +- Builds the package +- Publishes a preview version via [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) +- Adds a comment to PRs with installation instructions + +### Publish to npm (`publish.yml`) +Runs when a GitHub release is created: +- Builds the package +- Publishes to npm with the `--access public` flag + +## Setup + +### For npm Publishing + +1. Create an npm access token: + - Go to https://www.npmjs.com/settings/YOUR_USERNAME/tokens + - Click "Generate New Token" → "Automation" + - Copy the token + +2. Add the token to your GitHub repository: + - Go to your repository Settings → Secrets and variables → Actions + - Click "New repository secret" + - Name: `NPM_TOKEN` + - Value: Paste your npm token + - Click "Add secret" + +3. Create a release: + - Go to your repository → Releases → Draft a new release + - Create a tag (e.g., `v0.1.0`) + - Click "Publish release" + - The workflow will automatically publish to npm + +### For pkg.pr.new + +No setup required! The workflow will automatically: +- Publish preview packages for every PR +- Comment on PRs with installation instructions +- Update the comment when new commits are pushed + +## Testing Locally + +Before creating a release, test the build: + +```bash +pnpm install +pnpm build +cd example && pnpm build +``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b646674 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build plugin + run: pnpm build + + - name: Build basic example + run: | + cd examples/basic + pnpm build + + - name: Build Vercel example + run: | + cd examples/vercel + pnpm build diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..8f2432c --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,33 @@ +name: Preview Package + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + - main + +jobs: + preview: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Publish preview + run: pnpx pkg-pr-new publish diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..5148a00 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,33 @@ +name: Publish to npm + +on: + release: + types: [created] + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Publish to npm + run: pnpm publish --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd9fa51 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Dependencies +node_modules/ +.pnpm-store/ + +# Build output +dist/ +*.tsbuildinfo +.vite/ +.turbo/ + +# Environment files +.env +.env.local +.env.*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.code-workspace + +# OS files +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Testing +coverage/ + +# Temporary files +*.tmp +*.temp +.cache/ + +# Example build output +example/dist/ +example/api/ +example/public/ diff --git a/README.md b/README.md index e69de29..c0858e2 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,285 @@ +# vite-plugin-srvx + +A Vite plugin that integrates [srvx](https://srvx.h3.dev/) (Universal Server) with Vite's development server, similar to how `@hono/vite-dev-server` works with Hono. + +## Features + +- **Automatic index.html serving** - The plugin automatically serves `index.html` on the root path +- **Hot Module Replacement (HMR)** - Full HMR support for your srvx server +- **Automatic Vite client script injection** - Vite's dev client is automatically injected into HTML responses +- **Web Standard APIs** - Use Request/Response APIs that work everywhere +- **Universal** - Works with Node.js, Deno, and Bun +- **Lightning fast** - Powered by Vite's blazing fast dev server +- **Vercel Edge Functions** - Built-in support for deploying to Vercel (auto-detected!) + +## Installation + +```bash +npm install vite-plugin-srvx srvx vite +``` + +Or with pnpm: + +```bash +pnpm add vite-plugin-srvx srvx vite +``` + +## Usage + +### 1. Create an index.html file + +Create an `index.html` file in your project root: + +```html + + + + + + My srvx App + + +

Hello from srvx + Vite!

+ + + +``` + +### 2. Create your srvx server + +Create a `src/server.ts` file for your API routes: + +```typescript +export default { + async fetch(request: Request): Promise { + const url = new URL(request.url) + + // The plugin automatically serves index.html on '/' + // so you only need to handle API routes here + + if (url.pathname === '/api/hello') { + return Response.json({ + message: 'Hello from srvx!', + timestamp: new Date().toISOString(), + }) + } + + return new Response('Not Found', { status: 404 }) + }, +} +``` + +### 3. Configure Vite + +Create a `vite.config.ts` file: + +```typescript +import { defineConfig } from 'vite' +import srvx from 'vite-plugin-srvx' + +export default defineConfig(({ mode }) => ({ + build: { + outDir: mode === 'server' ? 'dist' : 'dist/public', + }, + plugins: [ + ...srvx({ + entry: './src/server.ts', + }), + ], +})) +``` + +### 4. Run the development server + +```bash +vite +``` + +Your srvx server will now run with Vite's dev server! Visit `http://localhost:5173` to see your app: +- `/` - Automatically serves `index.html` +- `/api/hello` - Your srvx API endpoint +- All other routes are handled by your srvx server's fetch handler + +## Building for Production + +The plugin uses Vite's mode system to handle client and server builds separately. + +Add these scripts to your `package.json`: + +```json +{ + "scripts": { + "dev": "vite", + "build": "npm run build:client && npm run build:server", + "build:client": "vite build", + "build:server": "vite build --mode server", + "preview": "srvx dist/server.js" + } +} +``` + +Then build your app: + +```bash +npm run build +``` + +This will: +1. Build your frontend (HTML, CSS, JS) to `dist/public` +2. Build your srvx server to `dist/server.js` + +Run your production build: + +```bash +npm run preview +# or directly: srvx dist/server.js +``` + +srvx automatically serves static files from the `dist/public` directory! + +## Options + +### Plugin Options + +```typescript +interface SrvxOptions { + // Entry file for your srvx server (default: './src/server.ts') + entry?: string + + // Output directory for server build (default: 'dist') + outDir?: string + + // Server output filename (default: 'server.js') + serverOutFile?: string + + // Development server options + // Patterns to exclude from the srvx handler (will be handled by Vite instead) + exclude?: (string | RegExp)[] + + // Whether to inject Vite's client script for HMR (default: true) + injectClientScript?: boolean + + // Custom module loader (default: uses Vite's ssrLoadModule) + loadModule?: (server: ViteDevServer, entry: string) => Promise +} +``` + +> **Note:** The plugin returns an array of two plugins (dev server + build), so use the spread operator: `...srvx({})` + +### Example with custom options + +```typescript +import { defineConfig } from 'vite' +import srvx from 'vite-plugin-srvx' + +export default defineConfig(({ mode }) => ({ + build: { + outDir: mode === 'server' ? 'build' : 'build/public', + }, + plugins: [ + ...srvx({ + entry: './src/server.ts', + outDir: 'build', + serverOutFile: 'app.js', + exclude: [ + /.*\.tsx?$/, + /.*\.css$/, + /^\/@.+$/, + ], + injectClientScript: true, + }), + ], +})) +``` + +Then build with: +```bash +npm run build:client # builds to build/public +npm run build:server # builds to build/app.js +``` + +And run: `srvx build/app.js` (it will serve static files from `build/public`) + +### Using Individual Plugins (Advanced) + +If you need more control, you can import the plugins separately: + +```typescript +import { defineConfig } from 'vite' +import { devServer, srvxBuild } from 'vite-plugin-srvx' + +export default defineConfig(({ mode }) => ({ + build: { + outDir: mode === 'server' ? 'dist' : 'dist/public', + }, + plugins: [ + devServer({ entry: './src/server.ts' }), + srvxBuild({ entry: './src/server.ts' }), + ], +})) +``` + +## How it works + +### Development Mode + +The `devServer` plugin creates a Vite middleware that: + +1. **Serves index.html on root** - When requesting `/`, the plugin automatically serves and transforms your `index.html` using Vite's `transformIndexHtml` (which handles script injection, etc.) +2. **Intercepts other HTTP requests** - All non-root requests are passed to your srvx server +3. **Loads your srvx server module** - Uses Vite's SSR module loader for HMR support +4. **Converts to Web Standard APIs** - Converts Node.js `IncomingMessage` → Web Standard `Request` +5. **Calls your fetch handler** - Your srvx server's `fetch` handler processes the request +6. **Converts the response** - Converts the `Response` back to Node.js `ServerResponse` +7. **Injects Vite client** - For HTML responses from your server, Vite's client script is injected for HMR + +### Production Build + +The `srvxBuild` plugin uses Vite's mode system: + +1. **Client build** (`vite build`): + - Builds frontend to `dist/public` + - Plugin is inactive (mode !== 'server') + +2. **Server build** (`vite build --mode server`): + - Plugin activates (mode === 'server') + - Sets `ssr: true` via the `config` hook + - Builds server to `dist/server.js` + +3. **Run with srvx**: + - `srvx dist/server.js` + - srvx automatically serves static files from `dist/public` + +This approach follows the same pattern as [@hono/vite-build](https://github.com/honojs/vite-plugins/tree/main/packages/build) + +This gives you the best of both worlds: srvx's universal server API and Vite's lightning-fast development experience! + +## Example + +Check out the [example](./example) directory for a full working example. + +To run the example: + +```bash +pnpm install +pnpm build +cd example +pnpm dev +``` + +## Comparison with @hono/vite-dev-server + +This plugin is heavily inspired by `@hono/vite-dev-server` but designed specifically for srvx: + +- Similar middleware architecture +- Same HMR capabilities +- Compatible API design +- Works with any framework that uses Web Standard fetch API + +## License + +MIT + +## Credits + +Inspired by [@hono/vite-dev-server](https://github.com/honojs/vite-plugins/tree/main/packages/dev-server) diff --git a/examples/basic/frontend/main.ts b/examples/basic/frontend/main.ts new file mode 100644 index 0000000..b098194 --- /dev/null +++ b/examples/basic/frontend/main.ts @@ -0,0 +1 @@ +console.log("Hello! This is the client module."); diff --git a/examples/basic/index.html b/examples/basic/index.html new file mode 100644 index 0000000..2758ad7 --- /dev/null +++ b/examples/basic/index.html @@ -0,0 +1,86 @@ + + + + + + srvx + Vite Example + + + +

🚀 srvx + Vite

+

Universal Server powered by srvx, running with Vite dev server

+ +
+

Available Routes

+ +
+ +
+

Features

+
    +
  • ✨ Hot Module Replacement (HMR)
  • +
  • 🔥 Web Standard APIs (Request/Response)
  • +
  • ⚡ Lightning fast with Vite
  • +
  • 🌐 Universal - works with Node.js, Deno, Bun
  • +
+
+ + + + + diff --git a/examples/basic/package.json b/examples/basic/package.json new file mode 100644 index 0000000..6256b6f --- /dev/null +++ b/examples/basic/package.json @@ -0,0 +1,20 @@ +{ + "name": "srvx-vite-example", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build && vite build --mode server", + "start": "srvx --static public dist/server.js" + }, + "dependencies": { + "srvx": "^0.9.7" + }, + "devDependencies": { + "@types/node": "^22.10.5", + "typescript": "^5.7.3", + "vite": "^6.0.5", + "vite-plugin-srvx": "workspace:*" + } +} diff --git a/examples/basic/src/server.ts b/examples/basic/src/server.ts new file mode 100644 index 0000000..9e8739e --- /dev/null +++ b/examples/basic/src/server.ts @@ -0,0 +1,65 @@ +export default { + async fetch(request: Request): Promise { + const url = new URL(request.url); + + if (url.pathname === "/api/hello") { + return Response.json({ + message: "Hello from srvx!", + timestamp: new Date().toISOString(), + powered_by: "srvx + Vite", + }); + } + + if (url.pathname === "/api/time") { + return Response.json({ + time: new Date().toLocaleTimeString(), + date: new Date().toLocaleDateString(), + unix: Date.now(), + }); + } + + if (url.pathname === "/about") { + return new Response( + ` + + + + + About - srvx + Vite + + + +

About

+

This is an example app demonstrating the integration of srvx with Vite.

+

srvx is a universal server framework based on web standards, and Vite provides lightning-fast development experience.

+

← Back to home

+ +`, + { + headers: { + "Content-Type": "text/html", + }, + }, + ); + } + + return new Response("Not Found", { status: 404 }); + }, +}; diff --git a/examples/basic/tsconfig.json b/examples/basic/tsconfig.json new file mode 100644 index 0000000..7e31a45 --- /dev/null +++ b/examples/basic/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022", "DOM"], + "moduleResolution": "bundler", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src", "frontend"] +} diff --git a/examples/basic/vite.config.ts b/examples/basic/vite.config.ts new file mode 100644 index 0000000..e8e825e --- /dev/null +++ b/examples/basic/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vite"; +import srvx from "vite-plugin-srvx"; + +export default defineConfig({ + plugins: [ + ...srvx({ + entry: "./src/server.ts", + }), + ], +}); diff --git a/examples/vercel/.gitignore b/examples/vercel/.gitignore new file mode 100644 index 0000000..8cf80cd --- /dev/null +++ b/examples/vercel/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +node_modules/ + +# Build output +dist/ + +# Vercel +.vercel/ + +# Environment +.env +.env.local +.env*.local + +# Logs +*.log + +# OS +.DS_Store +.vercel diff --git a/examples/vercel/README.md b/examples/vercel/README.md new file mode 100644 index 0000000..f37b302 --- /dev/null +++ b/examples/vercel/README.md @@ -0,0 +1,223 @@ +# Vercel Edge Functions Example + +This example demonstrates how to deploy a **srvx** server as a **Vercel Edge Function** using **vite-plugin-srvx**. + +## Features + +- ⚡ **Vite** for lightning-fast development +- 🌐 **srvx** Universal Server with Web Standard APIs +- ▲ **Vercel Edge Functions** for global edge deployment +- 🎨 Interactive frontend with API testing +- 🔧 Zero-config deployment setup + +## Quick Start + +### 1. Install Dependencies + +```bash +pnpm install +``` + +### 2. Development + +```bash +pnpm dev +``` + +Visit http://localhost:5173 to see the app in development mode. + +### 3. Build + +```bash +pnpm build +``` + +This creates: +``` +dist/ +├── api/ +│ └── index.js # Vercel Edge Function +└── public/ # Static assets + ├── index.html + └── assets/ +``` + +### 4. Preview with Vercel CLI + +```bash +pnpm preview +``` + +Or manually: + +```bash +vercel dev +``` + +## Deploy to Vercel + +### Option 1: Deploy via CLI + +```bash +# Install Vercel CLI if you haven't +npm i -g vercel + +# Deploy +vercel +``` + +### Option 2: Deploy via GitHub + +1. Push this example to a GitHub repository +2. Import the repository in [Vercel Dashboard](https://vercel.com/new) +3. Vercel will automatically detect the configuration and deploy! + +## How It Works + +### 1. Vite Configuration + +The `vite.config.ts` enables Vercel mode: + +```typescript +import { defineConfig } from "vite"; +import srvx from "vite-plugin-srvx"; + +export default defineConfig({ + plugins: [ + ...srvx({ + entry: "./src/server.ts", + framework: "vercel", // Enable Vercel Edge Functions + }), + ], +}); +``` + +### 2. Vercel Configuration + +The `vercel.json` tells Vercel where to find the Edge Function: + +```json +{ + "buildCommand": "pnpm build", + "outputDirectory": "dist/public", + "functions": { + "dist/api/**": { + "runtime": "edge" + } + } +} +``` + +### 3. Server Code + +Your srvx server (`src/server.ts`) uses Web Standard APIs: + +```typescript +export default { + async fetch(request: Request): Promise { + const url = new URL(request.url); + + if (url.pathname === "/api/hello") { + return Response.json({ message: "Hello!" }); + } + + return new Response("Not Found", { status: 404 }); + }, +}; +``` + +## API Endpoints + +The example includes several API endpoints: + +- **`GET /api/hello`** - Returns a greeting message +- **`GET /api/time`** - Returns current time +- **`GET /api/user-agent`** - Returns user agent and headers + +## Edge Function Benefits + +Deploying to Vercel Edge Functions provides: + +- ⚡ **Ultra-low latency** - Runs on Vercel's global edge network +- 🌍 **Global reach** - Automatically deployed to 100+ edge locations +- 📈 **Auto-scaling** - Scales automatically based on demand +- 💰 **Cost-effective** - Pay only for what you use +- 🔒 **Secure** - Runs in isolated V8 environments + +## Project Structure + +``` +examples/vercel/ +├── frontend/ +│ ├── main.ts # Frontend TypeScript +│ └── style.css # Styles +├── src/ +│ └── server.ts # srvx server (Edge Function) +├── index.html # Entry HTML +├── package.json # Dependencies +├── tsconfig.json # TypeScript config +├── vite.config.ts # Vite + srvx config +└── vercel.json # Vercel deployment config +``` + +## Environment Variables + +To use environment variables in your Edge Function: + +1. Add them in Vercel Dashboard → Project Settings → Environment Variables +2. Access them in your server code: + +```typescript +export default { + async fetch(request: Request) { + const apiKey = process.env.API_KEY; + // Use the API key... + }, +}; +``` + +## Limitations + +Vercel Edge Functions have some constraints: + +- **Max Duration**: 25 seconds (must begin sending response) +- **Streaming**: Up to 300 seconds +- **Code Size**: 1 MB (Hobby), 2 MB (Pro), 4 MB (Enterprise) after gzip +- **No Node.js APIs**: Only Web Standard APIs (which srvx uses!) + +## Troubleshooting + +### Build fails + +Make sure all dependencies are installed: +```bash +pnpm install +``` + +### Edge Function not found + +Verify your `vercel.json` has the correct function configuration: +```json +{ + "functions": { + "dist/api/**": { + "runtime": "edge" + } + } +} +``` + +### Static files not loading + +Check that your `outputDirectory` points to `dist/public` in `vercel.json`. + +## Learn More + +- [Vercel Edge Functions Documentation](https://vercel.com/docs/functions/runtimes/edge) +- [srvx Documentation](https://srvx.h3.dev/) +- [vite-plugin-srvx Documentation](../../README.md) +- [Vercel Deployment Guide](../../VERCEL.md) + +## License + +MIT diff --git a/examples/vercel/frontend/main.ts b/examples/vercel/frontend/main.ts new file mode 100644 index 0000000..c72323f --- /dev/null +++ b/examples/vercel/frontend/main.ts @@ -0,0 +1,72 @@ +import "./style.css"; + +const app = document.querySelector("#app")!; + +app.innerHTML = ` +
+
+

⚡ Vercel Edge Functions

+

Powered by srvx + Vite

+
+ ⚡ Vite + 🌐 srvx + ▲ Vercel Edge +
+
+ +
+

Try the API Endpoints

+
+ + + +
+
+ +
+

Response:

+
Click a button above to test an API endpoint
+
+ + + +
+

Deployed on Vercel Edge Functions • Lightning fast global CDN

+
+
+`; + +// Add event listeners to API buttons +const buttons = document.querySelectorAll(".api-btn"); +const responseBox = document.querySelector("#response")!; + +buttons.forEach((button) => { + button.addEventListener("click", async () => { + const endpoint = button.dataset.endpoint!; + responseBox.textContent = "Loading..."; + responseBox.classList.add("loading"); + + try { + const response = await fetch(endpoint); + const data = await response.json(); + responseBox.textContent = JSON.stringify(data, null, 2); + responseBox.classList.remove("loading"); + responseBox.classList.add("success"); + } catch (error) { + responseBox.textContent = `Error: ${error}`; + responseBox.classList.remove("loading"); + responseBox.classList.add("error"); + } + }); +}); diff --git a/examples/vercel/frontend/style.css b/examples/vercel/frontend/style.css new file mode 100644 index 0000000..6a47cae --- /dev/null +++ b/examples/vercel/frontend/style.css @@ -0,0 +1,172 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen, Ubuntu, Cantarell, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + min-height: 100vh; + padding: 2rem; +} + +.container { + max-width: 900px; + margin: 0 auto; +} + +header { + text-align: center; + margin-bottom: 3rem; +} + +h1 { + font-size: 3.5rem; + margin-bottom: 0.5rem; + font-weight: 800; +} + +.subtitle { + font-size: 1.5rem; + opacity: 0.9; + margin-bottom: 1rem; +} + +.badges { + display: flex; + gap: 0.5rem; + justify-content: center; + flex-wrap: wrap; +} + +.badge { + background: rgba(255, 255, 255, 0.2); + padding: 0.5rem 1rem; + border-radius: 2rem; + font-size: 0.9rem; + backdrop-filter: blur(10px); +} + +.features { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border-radius: 1rem; + padding: 2rem; + margin-bottom: 2rem; +} + +h2 { + font-size: 2rem; + margin-bottom: 1.5rem; + text-align: center; +} + +h3 { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.api-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; +} + +.api-btn { + background: rgba(255, 255, 255, 0.15); + border: 2px solid rgba(255, 255, 255, 0.3); + color: white; + padding: 1.5rem; + border-radius: 0.75rem; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.api-btn:hover { + background: rgba(255, 255, 255, 0.25); + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); +} + +.api-btn:active { + transform: translateY(0); +} + +.api-btn .emoji { + font-size: 2rem; +} + +.response-section { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border-radius: 1rem; + padding: 2rem; + margin-bottom: 2rem; +} + +.response-box { + background: rgba(0, 0, 0, 0.3); + padding: 1.5rem; + border-radius: 0.5rem; + overflow-x: auto; + font-family: "Courier New", monospace; + font-size: 0.9rem; + line-height: 1.6; + min-height: 150px; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.response-box.loading { + color: #ffd700; +} + +.response-box.success { + color: #90ee90; +} + +.response-box.error { + color: #ff6b6b; +} + +.links { + text-align: center; + margin-bottom: 2rem; +} + +.link { + color: #ffd700; + text-decoration: none; + font-size: 1.2rem; + font-weight: 600; + transition: all 0.3s ease; + display: inline-block; +} + +.link:hover { + transform: translateX(5px); + text-decoration: underline; +} + +footer { + text-align: center; + opacity: 0.8; + font-size: 0.9rem; +} + +@media (max-width: 768px) { + h1 { + font-size: 2.5rem; + } + + .api-grid { + grid-template-columns: 1fr; + } +} diff --git a/examples/vercel/index.html b/examples/vercel/index.html new file mode 100644 index 0000000..a505b9d --- /dev/null +++ b/examples/vercel/index.html @@ -0,0 +1,12 @@ + + + + + + Vercel Edge Functions with srvx + Vite + + +
+ + + diff --git a/examples/vercel/package.json b/examples/vercel/package.json new file mode 100644 index 0000000..70e22f4 --- /dev/null +++ b/examples/vercel/package.json @@ -0,0 +1,20 @@ +{ + "name": "srvx-vite-vercel-example", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build && vite build --mode server", + "preview": "vercel dev" + }, + "dependencies": { + "srvx": "^0.9.7" + }, + "devDependencies": { + "@types/node": "^22.10.5", + "typescript": "^5.7.3", + "vite": "^6.0.5", + "vite-plugin-srvx": "workspace:*" + } +} diff --git a/examples/vercel/src/server.ts b/examples/vercel/src/server.ts new file mode 100644 index 0000000..34e7341 --- /dev/null +++ b/examples/vercel/src/server.ts @@ -0,0 +1,95 @@ +export default { + async fetch(request: Request): Promise { + const url = new URL(request.url); + + // API endpoint - returns JSON + if (url.pathname === "/api/hello") { + return Response.json({ + message: "Hello from Vercel Edge Functions!", + timestamp: new Date().toISOString(), + powered_by: "srvx + Vite + Vercel Edge", + runtime: "edge", + }); + } + + // Time API endpoint + if (url.pathname === "/api/time") { + return Response.json({ + time: new Date().toLocaleTimeString(), + date: new Date().toLocaleDateString(), + unix: Date.now(), + }); + } + + // User agent endpoint + if (url.pathname === "/api/user-agent") { + const userAgent = request.headers.get("user-agent") || "Unknown"; + return Response.json({ + userAgent, + headers: Object.fromEntries(request.headers.entries()), + }); + } + + // About page - returns HTML + if (url.pathname === "/about") { + return new Response( + ` + + + + + About - Vercel Edge Example + + + +

About This Example

+

+ This is a demonstration of srvx (Universal Server) + deployed as a Vercel Edge Function, with + Vite powering the development and build process. +

+
+ ⚡ Vite + 🌐 srvx + ▲ Vercel Edge +
+

← Back to home

+ +`, + { + headers: { + "Content-Type": "text/html", + }, + } + ); + } + + // For all other routes, return 404 + return new Response("Not Found", { status: 404 }); + }, +}; diff --git a/examples/vercel/tsconfig.json b/examples/vercel/tsconfig.json new file mode 100644 index 0000000..c51b7df --- /dev/null +++ b/examples/vercel/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "frontend"] +} diff --git a/examples/vercel/vercel.json b/examples/vercel/vercel.json new file mode 100644 index 0000000..7d5c195 --- /dev/null +++ b/examples/vercel/vercel.json @@ -0,0 +1,9 @@ +{ + "buildCommand": "pnpm build", + "outputDirectory": "dist/public", + "functions": { + "dist/api/**": { + "runtime": "edge" + } + } +} diff --git a/examples/vercel/vite.config.ts b/examples/vercel/vite.config.ts new file mode 100644 index 0000000..563432b --- /dev/null +++ b/examples/vercel/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vite"; +import srvx from "vite-plugin-srvx"; + +export default defineConfig({ + plugins: [ + ...srvx({ + entry: "./src/server.ts", + framework: "vercel", // Enable Vercel Edge Functions + }), + ], +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..d8f416d --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "vite-plugin-srvx", + "version": "0.1.0", + "description": "Vite plugin for srvx - Universal Server integration", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit" + }, + "keywords": [ + "vite", + "vite-plugin", + "srvx", + "server", + "ssr" + ], + "author": "", + "license": "MIT", + "peerDependencies": { + "srvx": ">=0.9.0", + "vite": "^5.0.0 || ^6.0.0" + }, + "devDependencies": { + "@types/node": "^22.10.5", + "srvx": "^0.9.7", + "tsup": "^8.3.5", + "typescript": "^5.7.3", + "vite": "^6.0.5" + }, + "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..f33e82c --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1309 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/node': + specifier: ^22.10.5 + version: 22.19.3 + srvx: + specifier: ^0.9.7 + version: 0.9.8 + tsup: + specifier: ^8.3.5 + version: 8.5.1(postcss@8.5.6)(typescript@5.9.3) + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vite: + specifier: ^6.0.5 + version: 6.4.1(@types/node@22.19.3) + + examples/basic: + dependencies: + srvx: + specifier: ^0.9.7 + version: 0.9.8 + devDependencies: + '@types/node': + specifier: ^22.10.5 + version: 22.19.3 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vite: + specifier: ^6.0.5 + version: 6.4.1(@types/node@22.19.3) + vite-plugin-srvx: + specifier: workspace:* + version: link:../.. + + examples/vercel: + dependencies: + srvx: + specifier: ^0.9.7 + version: 0.9.8 + devDependencies: + '@types/node': + specifier: ^22.10.5 + version: 22.19.3 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + vite: + specifier: ^6.0.5 + version: 6.4.1(@types/node@22.19.3) + vite-plugin-srvx: + specifier: workspace:* + version: link:../.. + +packages: + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@22.19.3': + resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + srvx@0.9.8: + resolution: {integrity: sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ==} + engines: {node: '>=20.16.0'} + hasBin: true + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + +snapshots: + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true + + '@rollup/rollup-darwin-x64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.55.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.55.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.55.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.55.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.55.1': + optional: true + + '@types/estree@1.0.8': {} + + '@types/node@22.19.3': + dependencies: + undici-types: 6.21.0 + + acorn@8.15.0: {} + + any-promise@1.3.0: {} + + bundle-require@5.1.0(esbuild@0.27.2): + dependencies: + esbuild: 0.27.2 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + commander@4.1.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.0 + rollup: 4.55.1 + + fsevents@2.3.3: + optional: true + + joycon@3.1.1: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + object-assign@4.1.1: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + postcss-load-config@6.0.1(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.6 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + readdirp@4.1.2: {} + + resolve-from@5.0.0: {} + + rollup@4.55.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + srvx@0.9.8: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tree-kill@1.2.2: {} + + ts-interface-checker@0.1.13: {} + + tsup@8.5.1(postcss@8.5.6)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.2) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.2 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.6) + resolve-from: 5.0.0 + rollup: 4.55.1 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.6 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + typescript@5.9.3: {} + + ufo@1.6.1: {} + + undici-types@6.21.0: {} + + vite@6.4.1(@types/node@22.19.3): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.55.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.3 + fsevents: 2.3.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..471ec4b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - '.' + - 'examples/*' diff --git a/src/build.ts b/src/build.ts new file mode 100644 index 0000000..8ec2e59 --- /dev/null +++ b/src/build.ts @@ -0,0 +1,85 @@ +import path from 'path' +import type { Plugin, UserConfig } from 'vite' + +export interface BuildOptions { + entry?: string + outDir?: string + serverOutFile?: string + framework?: 'vercel' // Target framework for deployment +} + +export const defaultBuildOptions: Partial = { + entry: './src/server.ts', + outDir: 'dist', + serverOutFile: 'server.js', +} + +export function srvxBuild(options?: BuildOptions): Plugin { + const mergedOptions = { ...defaultBuildOptions, ...options } + + // Auto-detect Vercel or use explicit framework option + const isVercel = mergedOptions.framework === 'vercel' || process.env.VERCEL === '1' + + return { + name: 'vite-plugin-srvx-server-build', + + // Only apply during server builds (not client builds) + apply(_config, { command, mode }) { + return command === 'build' && mode === 'server' + }, + + config(): UserConfig { + // For Vercel, output to dist/api/index.js + // For standard builds, use configured options + const serverOutDir = isVercel + ? path.join(mergedOptions.outDir || 'dist', 'api') + : (mergedOptions.outDir || 'dist') + const serverOutFile = isVercel ? 'index.js' : (mergedOptions.serverOutFile || 'server.js') + + return { + build: { + ssr: true, + outDir: serverOutDir, + copyPublicDir: false, // Don't copy public assets during server build + rollupOptions: { + input: mergedOptions.entry, + output: { + entryFileNames: serverOutFile, + format: 'esm', + }, + }, + emptyOutDir: false, + }, + } + }, + + buildEnd() { + const serverOutDir = isVercel + ? path.join(mergedOptions.outDir || 'dist', 'api') + : (mergedOptions.outDir || 'dist') + const serverOutFile = isVercel ? 'index.js' : (mergedOptions.serverOutFile || 'server.js') + const serverPath = path.join(serverOutDir, serverOutFile) + + console.log('\n✅ Server built successfully!') + console.log(`📦 Server output: ${serverPath}`) + + if (isVercel) { + console.log('\n🚀 Deploy to Vercel:') + console.log(' 1. Configure vercel.json with:') + console.log(' {') + console.log(' "functions": {') + console.log(' "dist/api/**": { "runtime": "edge" }') + console.log(' }') + console.log(' }') + console.log(' 2. Run: vercel deploy\n') + console.log('💡 Output structure:') + console.log(' dist/api/index.js - Edge Function') + console.log(' dist/public/ - Static assets') + } else { + console.log('\n🚀 Run your app with:') + console.log(` srvx ${serverPath}\n`) + console.log('💡 Tip: srvx will automatically serve static files from dist/public/') + } + }, + } +} diff --git a/src/client-build.ts b/src/client-build.ts new file mode 100644 index 0000000..10d129b --- /dev/null +++ b/src/client-build.ts @@ -0,0 +1,34 @@ +import path from 'path' +import type { Plugin, UserConfig } from 'vite' + +export interface ClientBuildOptions { + outDir?: string +} + +export const defaultClientBuildOptions: Required = { + outDir: 'dist', +} + +export function clientBuild(options?: ClientBuildOptions): Plugin { + const mergedOptions = { ...defaultClientBuildOptions, ...options } + + return { + name: 'vite-plugin-srvx-client-build', + + // Only apply during client builds (not server builds) + apply(_config, { command, mode }) { + return command === 'build' && mode !== 'server' + }, + + config(): UserConfig { + // Output to 'dist/public' (or custom outDir/public) + const clientOutDir = path.join(mergedOptions.outDir, 'public') + + return { + build: { + outDir: clientOutDir, + }, + } + }, + } +} diff --git a/src/dev-server.ts b/src/dev-server.ts new file mode 100644 index 0000000..85d298b --- /dev/null +++ b/src/dev-server.ts @@ -0,0 +1,199 @@ +import type { Plugin, ViteDevServer } from 'vite' +import type { IncomingMessage, ServerResponse } from 'http' +import fs from 'fs' +import path from 'path' + +export interface DevServerOptions { + entry?: string + exclude?: (string | RegExp)[] + injectClientScript?: boolean + loadModule?: (server: ViteDevServer, entry: string) => Promise +} + +export const defaultOptions: Partial = { + entry: './src/server.ts', + exclude: [ + /.*\.tsx?$/, + /.*\.ts$/, + /.*\.jsx?$/, + /.*\.css$/, + /.*\.scss$/, + /.*\.sass$/, + /.*\.less$/, + /.*\.styl$/, + /.*\.png$/, + /.*\.jpg$/, + /.*\.jpeg$/, + /.*\.gif$/, + /.*\.svg$/, + /.*\.webp$/, + /^\/@.+$/, + /^\/node_modules\/.*/, + /\?import$/, + ], + injectClientScript: true, +} + +interface SrvxApp { + fetch: (request: Request) => Response | Promise +} + +function createMiddleware(server: ViteDevServer, options: DevServerOptions) { + return async ( + req: IncomingMessage, + res: ServerResponse, + next: () => void + ) => { + const config = server.config + const base = config.base === '/' ? '' : config.base + + if (req.url === '/' || req.url === base || req.url === `${base}/`) { + const indexPath = path.join(config.root, 'index.html') + if (fs.existsSync(indexPath)) { + const html = await server.transformIndexHtml( + req.url, + fs.readFileSync(indexPath, 'utf-8') + ) + res.statusCode = 200 + res.setHeader('Content-Type', 'text/html') + res.setHeader('Content-Length', Buffer.byteLength(html)) + res.end(html) + return + } + } + + const exclude = options.exclude ?? defaultOptions.exclude ?? [] + + for (const pattern of exclude) { + if (req.url) { + if (pattern instanceof RegExp) { + if (pattern.test(req.url)) { + return next() + } + } else if (typeof pattern === 'string') { + if (req.url.startsWith(pattern)) { + return next() + } + } + } + } + + if (req.url?.startsWith(base)) { + const publicDir = config.publicDir + if (publicDir && fs.existsSync(publicDir)) { + const filePath = path.join(publicDir, req.url.replace(base, '')) + if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + return next() + } + } + } + + let app: SrvxApp | undefined + + try { + const loadModule = options.loadModule ?? ((server, entry) => server.ssrLoadModule(entry)) + const module = await loadModule(server, options.entry!) + + if ('default' in module) { + app = module.default as SrvxApp + } else { + app = module as SrvxApp + } + + if (!app?.fetch) { + throw new Error('No fetch handler found in the entry module') + } + } catch (e) { + return next() + } + + const protocol = (req.socket as any).encrypted ? 'https' : 'http' + const host = req.headers.host || 'localhost' + const url = `${protocol}://${host}${req.url}` + + const headers = new Headers() + for (const [key, value] of Object.entries(req.headers)) { + if (value !== undefined) { + if (Array.isArray(value)) { + for (const v of value) { + headers.append(key, v) + } + } else { + headers.set(key, value) + } + } + } + + const body = req.method !== 'GET' && req.method !== 'HEAD' ? req : undefined + + const request = new Request(url, { + method: req.method, + headers, + body: body as any, + }) + + const response = await app.fetch(request) + + res.statusCode = response.status + response.headers.forEach((value, key) => { + res.setHeader(key, value) + }) + + if (options.injectClientScript !== false) { + const contentType = response.headers.get('content-type') + if (contentType?.includes('text/html')) { + const body = await response.text() + + let script = `` + + const nonce = response.headers.get('content-security-policy-nonce') + if (nonce) { + script = `` + } + + const injectedBody = body.replace('', `${script}`) + + res.setHeader('content-length', Buffer.byteLength(injectedBody)) + res.end(injectedBody) + return + } + } + + if (response.body) { + const reader = response.body.getReader() + const stream = async () => { + try { + while (true) { + const { done, value } = await reader.read() + if (done) break + res.write(value) + } + res.end() + } catch (error) { + res.end() + } + } + await stream() + } else { + res.end() + } + } +} + +export function devServer(options?: DevServerOptions): Plugin { + const entry = options?.entry ?? defaultOptions.entry + + return { + name: 'vite-plugin-srvx-dev', + apply: 'serve', + configureServer(server) { + const mergedOptions: DevServerOptions = { + ...defaultOptions, + ...options, + entry, + } + + server.middlewares.use(createMiddleware(server, mergedOptions)) + }, + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..883ed93 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,15 @@ +import { srvx } from "./srvx"; + +export type { BuildOptions } from "./build"; +export { defaultBuildOptions, srvxBuild } from "./build"; +export type { ClientBuildOptions } from "./client-build"; +export { clientBuild, defaultClientBuildOptions } from "./client-build"; +export type { DevServerOptions } from "./dev-server"; +// Individual plugins (for advanced use cases) +export { defaultOptions, devServer } from "./dev-server"; +export type { SrvxOptions } from "./srvx"; +// Unified plugin (recommended) +export { defaultSrvxOptions, srvx } from "./srvx"; + +// Default export is the unified plugin +export default srvx; diff --git a/src/srvx.ts b/src/srvx.ts new file mode 100644 index 0000000..88417cd --- /dev/null +++ b/src/srvx.ts @@ -0,0 +1,44 @@ +import type { Plugin } from "vite"; +import { srvxBuild } from "./build"; +import { clientBuild } from "./client-build"; +import { type DevServerOptions, devServer } from "./dev-server"; + +export interface SrvxOptions extends DevServerOptions { + // Build-specific options + outDir?: string; + serverOutFile?: string; + framework?: "vercel"; // Target framework for deployment +} + +export const defaultSrvxOptions: Partial = { + entry: "./src/server.ts", + outDir: "dist", + serverOutFile: "server.js", +}; + +export function srvx(options?: SrvxOptions): Plugin[] { + const mergedOptions = { ...defaultSrvxOptions, ...options }; + + return [ + // Development server plugin + devServer({ + entry: mergedOptions.entry, + exclude: mergedOptions.exclude, + injectClientScript: mergedOptions.injectClientScript, + loadModule: mergedOptions.loadModule, + }), + + // Client build plugin + clientBuild({ + outDir: mergedOptions.outDir, + }), + + // Server build plugin + srvxBuild({ + entry: mergedOptions.entry, + outDir: mergedOptions.outDir, + serverOutFile: mergedOptions.serverOutFile, + framework: mergedOptions.framework, + }), + ]; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f538874 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "example"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..3e10e4f --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + clean: true, + sourcemap: true, + splitting: false, +})