try to get Vercel to play nice

This commit is contained in:
Paul Bakaus
2025-11-16 19:07:03 -08:00
parent 2e5d653273
commit 3ade148de1
8 changed files with 224 additions and 14 deletions
+4
View File
@@ -0,0 +1,4 @@
tests/
server/
*.test.js
+20 -9
View File
@@ -49,6 +49,12 @@ vibe-design-plugins/
│ │ └── prompts/*.md
│ ├── AGENTS.md
│ └── AGENTS.*.md
├── api/ # Vercel Functions (production)
│ ├── skills.js # GET /api/skills
│ ├── commands.js # GET /api/commands
│ └── download/
│ ├── [type]/[provider]/[id].js # Individual downloads
│ └── bundle/[provider].js # Bundle downloads
├── public/ # Website for impeccable.style
│ ├── index.html # Main page
│ ├── css/ # Modular CSS (9 files)
@@ -56,7 +62,7 @@ vibe-design-plugins/
│ │ ├── tokens.css # Design system
│ │ └── ... # Component styles
│ └── app.js # Vanilla JS
├── server/ # Bun server
├── server/ # Bun server (local dev only)
│ └── index.js # Serves website + API routes
├── scripts/ # Build system (Bun)
│ ├── build.js # Main orchestrator
@@ -78,9 +84,15 @@ vibe-design-plugins/
**Tech Stack:**
- Vanilla JavaScript (no frameworks)
- Modern CSS with Bun's bundler (nesting, OKLCH colors, @import)
- Bun server with HTML imports as routes
- **Local Development**: Bun server with native routes (`server/index.js`)
- **Production**: Vercel Functions with Bun runtime (`/api` directory)
- Deployed on Vercel with Bun runtime
**Dual Setup:**
- `/api` directory contains individual Vercel Functions for production
- `/server` directory contains monolithic Bun server for local development
- Same logic, different structure (Vercel requires serverless functions)
**Design:**
- Editorial precision aesthetic
- Cormorant Garamond (display) + Instrument Sans (body)
@@ -88,13 +100,12 @@ vibe-design-plugins/
- Editorial sidebar layout (title left, content right)
- Modular CSS architecture (9 files)
**Routes:**
- `/` - Homepage
- `/api/skills` - JSON list
- `/api/commands` - JSON list
- `/api/download/skill/:provider/:id` - Individual file
- `/api/download/command/:provider/:id` - Individual file
- `/api/download/bundle/:provider` - ZIP bundle
**API Endpoints** (Vercel Functions):
- `/` - Homepage (static HTML)
- `/api/skills` - JSON list of all skills
- `/api/commands` - JSON list of all commands
- `/api/download/[type]/[provider]/[id]` - Individual file download
- `/api/download/bundle/[provider]` - ZIP bundle download
## Source File Format
+13 -4
View File
@@ -269,18 +269,27 @@ bun run dev
- `source/` - Edit these! Single source of truth for all content
- `dist/` - Generated provider-specific files (committed for users)
- `api/` - Vercel Functions for production (serverless)
- `public/` - Website (HTML, CSS modules, vanilla JS)
- `server/` - Bun server for impeccable.style
- `server/` - Bun server for local development
- `scripts/` - Build system that transforms source → dist
### Deployment
The site runs on Vercel with Bun runtime. Just push to main and it auto-deploys.
The site runs on [Vercel](https://vercel.com) with [Bun runtime](https://vercel.com/docs/functions/runtimes/bun).
**Architecture:**
- **Local dev**: Monolithic Bun server (`server/index.js`) with native routing
- **Production**: Individual Vercel Functions (`api/` directory) for serverless deployment
Push to main and Vercel auto-deploys:
```bash
# Production build
# Build generates dist/ files and ZIPs
bun run build
bun run start # Uses --production flag
# For local testing
bun run dev # Uses server/index.js
```
See [DEVELOP.md](DEVELOP.md) for detailed contributor guidelines.
+39
View File
@@ -0,0 +1,39 @@
import { readdir } from "fs/promises";
import { join } from "path";
// Read all commands from source directory
async function getCommands() {
const sourceDir = join(process.cwd(), "source");
const commandsDir = join(sourceDir, "commands");
const files = await readdir(commandsDir);
const commands = [];
for (const file of files) {
if (file.endsWith(".md")) {
const content = await Bun.file(join(commandsDir, file)).text();
const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/);
if (frontmatterMatch) {
const frontmatter = frontmatterMatch[1];
const nameMatch = frontmatter.match(/name:\s*(.+)/);
const descMatch = frontmatter.match(/description:\s*(.+)/);
commands.push({
id: file.replace(".md", ""),
name: nameMatch?.[1]?.trim() || file.replace(".md", ""),
description: descMatch?.[1]?.trim() || "No description available",
});
}
}
}
return commands;
}
export default {
async fetch(request) {
const commands = await getCommands();
return Response.json(commands);
},
};
+72
View File
@@ -0,0 +1,72 @@
import { join, basename } from "path";
// Get the appropriate file path for a provider
function getFilePath(type, provider, id) {
const distDir = join(process.cwd(), "dist");
if (type === "skill") {
if (provider === "cursor") {
return join(distDir, "cursor", ".cursor", "rules", `${id}.md`);
} else if (provider === "claude-code") {
return join(distDir, "claude-code", ".claude", "skills", id, "SKILL.md");
} else if (provider === "gemini") {
return join(distDir, "gemini", `GEMINI.${id}.md`);
} else if (provider === "codex") {
return join(distDir, "codex", `AGENTS.${id}.md`);
}
} else if (type === "command") {
if (provider === "cursor") {
return join(distDir, "cursor", ".cursor", "commands", `${id}.md`);
} else if (provider === "claude-code") {
return join(distDir, "claude-code", ".claude", "commands", `${id}.md`);
} else if (provider === "gemini") {
return join(distDir, "gemini", ".gemini", "commands", `${id}.toml`);
} else if (provider === "codex") {
return join(distDir, "codex", ".codex", "prompts", `${id}.md`);
}
}
return null;
}
export default {
async fetch(request) {
const url = new URL(request.url);
const pathParts = url.pathname.split('/').filter(Boolean);
// Extract params from path: /api/download/[type]/[provider]/[id]
const type = pathParts[2]; // after 'api', 'download'
const provider = pathParts[3];
const id = pathParts[4];
if (type !== "skill" && type !== "command") {
return new Response("Invalid type", { status: 400 });
}
const filePath = getFilePath(type, provider, id);
if (!filePath) {
return new Response("Invalid provider", { status: 400 });
}
try {
const file = Bun.file(filePath);
const exists = await file.exists();
if (!exists) {
return new Response("File not found", { status: 404 });
}
const fileName = basename(filePath);
return new Response(file, {
headers: {
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename="${fileName}"`,
},
});
} catch (error) {
console.error("Error downloading file:", error);
return new Response("Error downloading file", { status: 500 });
}
},
};
+34
View File
@@ -0,0 +1,34 @@
import { join } from "path";
export default {
async fetch(request) {
const url = new URL(request.url);
const pathParts = url.pathname.split('/').filter(Boolean);
// Extract provider from path: /api/download/bundle/[provider]
const provider = pathParts[3]; // after 'api', 'download', 'bundle'
const distDir = join(process.cwd(), "dist");
const zipPath = join(distDir, `${provider}.zip`);
try {
const file = Bun.file(zipPath);
const exists = await file.exists();
if (!exists) {
return new Response("Bundle not found", { status: 404 });
}
return new Response(file, {
headers: {
"Content-Type": "application/zip",
"Content-Disposition": `attachment; filename="impeccable-style-${provider}.zip"`,
},
});
} catch (error) {
console.error("Error downloading bundle:", error);
return new Response("Error downloading bundle", { status: 500 });
}
},
};
+39
View File
@@ -0,0 +1,39 @@
import { readdir } from "fs/promises";
import { join } from "path";
// Read all skills from source directory
async function getSkills() {
const sourceDir = join(process.cwd(), "source");
const skillsDir = join(sourceDir, "skills");
const files = await readdir(skillsDir);
const skills = [];
for (const file of files) {
if (file.endsWith(".md")) {
const content = await Bun.file(join(skillsDir, file)).text();
const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/);
if (frontmatterMatch) {
const frontmatter = frontmatterMatch[1];
const nameMatch = frontmatter.match(/name:\s*(.+)/);
const descMatch = frontmatter.match(/description:\s*(.+)/);
skills.push({
id: file.replace(".md", ""),
name: nameMatch?.[1]?.trim() || file.replace(".md", ""),
description: descMatch?.[1]?.trim() || "No description available",
});
}
}
}
return skills;
}
export default {
async fetch(request) {
const skills = await getSkills();
return Response.json(skills);
},
};
+3 -1
View File
@@ -1,5 +1,7 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"bunVersion": "1.x"
"bunVersion": "1.x",
"buildCommand": "bun run build",
"outputDirectory": "public"
}