cloudflare-to-bun — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cloudflare-to-bun (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are assisting with migrating Cloudflare Workers applications to Bun. This involves converting edge runtime APIs, replacing Cloudflare bindings, and adapting from edge to server deployment.
For detailed patterns, see:
Check current setup:
# Check Cloudflare CLI
wrangler --version
# Check Bun installation
bun --version
# Analyze wrangler.toml
cat wrangler.tomlReview worker configuration:
# Check compatibility flags
grep compatibility_flags wrangler.toml
# Check bindings
grep -E "kv_namespaces|r2_buckets|d1_databases|durable_objects" wrangler.tomlDetermine what type of Worker you're migrating:
addEventListener('fetch') formatexport default { fetch() } format#### Basic Fetch Handler
Cloudflare Worker (Module format):
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
return new Response('Hello World');
},
};Bun Server:
Bun.serve({
port: 3000,
fetch(request: Request): Response | Promise<Response> {
return new Response('Hello World');
},
});
console.log('Server running on http://localhost:3000');#### Request/Response Handling
Cloudflare Worker:
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/api/data') {
return Response.json({ data: 'value' });
}
return new Response('Not found', { status: 404 });
},
};Bun (with Hono for routing):
import { Hono } from 'hono';
const app = new Hono();
app.get('/api/data', (c) => {
return c.json({ data: 'value' });
});
app.notFound((c) => {
return c.text('Not found', 404);
});
export default {
port: 3000,
fetch: app.fetch,
};For complete API mapping, see runtime-apis.md.
Cloudflare Workers use bindings for KV, R2, D1, etc. These need to be replaced:
#### KV Namespace → Database/Cache
Cloudflare Worker:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const value = await env.MY_KV.get('key');
await env.MY_KV.put('key', 'value', { expirationTtl: 3600 });
return Response.json({ value });
},
};Bun (using Redis or SQLite):
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
Bun.serve({
async fetch(request: Request) {
const value = await redis.get('key');
await redis.setex('key', 3600, 'value');
return Response.json({ value });
},
});#### R2 Bucket → S3 or File System
Cloudflare Worker:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const object = await env.MY_BUCKET.get('file.txt');
const text = await object?.text();
return new Response(text);
},
};Bun (using S3-compatible storage):
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: 'us-east-1' });
Bun.serve({
async fetch(request: Request) {
const command = new GetObjectCommand({
Bucket: 'my-bucket',
Key: 'file.txt',
});
const response = await s3.send(command);
const text = await response.Body?.transformToString();
return new Response(text);
},
});For all bindings replacements, see bindings.md.
Cloudflare Worker (wrangler.toml):
[vars]
API_KEY = "dev-key"
[[env.production.vars]]
API_KEY = "prod-key"Bun (.env files):
# .env.development
API_KEY=dev-key
# .env.production
API_KEY=prod-keyAccess in code:
// Both use the same API
const apiKey = process.env.API_KEY;wrangler.toml:
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[vars]
ENVIRONMENT = "production"
kv_namespaces = [
{ binding = "MY_KV", id = "..." }
]
r2_buckets = [
{ binding = "MY_BUCKET", bucket_name = "my-bucket" }
]package.json (Bun):
{
"name": "my-bun-app",
"type": "module",
"scripts": {
"dev": "bun run --hot src/index.ts",
"start": "bun run src/index.ts",
"build": "bun build src/index.ts --outdir=dist"
},
"dependencies": {
"hono": "^3.0.0",
"ioredis": "^5.0.0"
}
}Cloudflare Worker (manual routing):
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/') {
return new Response('Home');
}
if (url.pathname.startsWith('/api/')) {
return handleApi(request);
}
return new Response('Not found', { status: 404 });
},
};Bun (with Hono framework):
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c) => c.text('Home'));
const api = new Hono();
api.get('/users', (c) => c.json({ users: [] }));
app.route('/api', api);
export default {
port: 3000,
fetch: app.fetch,
};Cloudflare Worker:
export default {
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
await doCleanup();
},
};
// wrangler.toml
[triggers]
crons = ["0 0 * * *"] # Daily at midnightBun (using node-cron):
import cron from 'node-cron';
// Run daily at midnight
cron.schedule('0 0 * * *', async () => {
await doCleanup();
});
// Start server
Bun.serve({
fetch(request: Request) {
return new Response('Server running');
},
});Cloudflare Worker (Miniflare):
import { Miniflare } from 'miniflare';
const mf = new Miniflare({
script: `
export default {
fetch() { return new Response('Hello'); }
}
`,
});
const response = await mf.dispatchFetch('http://localhost/');Bun Test:
import { describe, test, expect } from 'bun:test';
describe('Server', () => {
test('should respond to requests', async () => {
const response = await fetch('http://localhost:3000/');
expect(response.status).toBe(200);
});
});Cloudflare Workers:
Bun Server:
For deployment strategies, see deployment.md.
{
"name": "migrated-from-cloudflare",
"type": "module",
"scripts": {
"dev": "bun run --hot src/index.ts",
"start": "NODE_ENV=production bun run src/index.ts",
"test": "bun test",
"build": "bun build src/index.ts --outdir=dist --minify"
},
"dependencies": {
"hono": "^3.11.0",
"ioredis": "^5.3.0",
"@aws-sdk/client-s3": "^3.478.0"
},
"devDependencies": {
"@types/bun": "latest"
}
}Cloudflare Worker:
cloudflare-worker/
├── src/
│ └── index.ts
├── wrangler.toml
└── package.jsonBun Server:
bun-server/
├── src/
│ ├── index.ts
│ ├── routes/
│ └── services/
├── .env.development
├── .env.production
├── package.json
├── tsconfig.json
└── bunfig.tomlbun install| Feature | Cloudflare Workers | Bun Server |
|---|---|---|
| Runtime | Edge (V8 isolates) | Server (JavaScriptCore) |
| Deployment | Global edge network | Traditional hosting |
| Cold Start | ~0ms | Minimal with Bun |
| Execution Time | Limited (CPU time) | Unlimited |
| Storage | KV, R2, D1, DO | Redis, S3, PostgreSQL, etc. |
| Cost Model | Per-request | Server/container costs |
| Scaling | Automatic | Manual/auto-scaling groups |
| State | Durable Objects | Traditional databases |
Same in both:
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type',
};
// Handle OPTIONS preflight
if (request.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}Same in both:
return Response.json({ data: 'value' }, {
headers: { 'Cache-Control': 'max-age=3600' }
});Same in both:
try {
// Your code
} catch (error) {
return new Response('Internal Server Error', { status: 500 });
}Once migration is complete, provide summary:
Suggest to the user:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.