cfify — Turn any TypeScript app into a Cloudflare Worker.
cfify inspects your repo, detects the framework and service deps that don't port to Workers, emits a wrangler.jsonc with the right bindings, a worker/ entry shim, and a migration-notes file that says exactly what to change by hand. Adapter architecture — new frameworks land by dropping in one file.
npm i -g cfify
# or run once, no install:
npx cfify .Against an Express API that uses ioredis, multer, pg, and node-cron:
$ cfify emit .
wrote worker/bindings.ts
wrote worker/index.ts
wrote worker/migration-notes.md
wrote wrangler.jsonc
warning: Refactor src/index.ts to export `app` and move `app.listen` behind a non-Workers guard.
warning: Fill REPLACE_ME resource ids in wrangler.jsonc with the ids printed by the provision commands.wrangler.jsonc — real output:
worker/index.ts wraps your Express app with httpServerHandler and adds a scheduled handler for the detected cron. worker/migration-notes.md lists the manual edits (swap ioredis → env.CACHE, multer → request.formData() + R2, etc.).
| Command | What it does |
|---|---|
cfify scan [path] |
Detect framework, package manager, and deps that don't port to Workers (Redis, BullMQ, Socket.IO, pg…). Lists blockers with severity. |
cfify detect [path] |
Run every adapter's detect() and rank matches by confidence. |
cfify adapters [path] |
List registered adapters and which claim this repo. |
cfify doctor [path] |
Check node ≥20, wrangler installed, Cloudflare auth, existing config files. Exits 1 if anything fails. |
cfify plan [path] |
Print a migration plan — generated files, blockers, manual work. Read-only. --adapter <name> to override detection. |
cfify emit [path] |
Write adapter-generated files (wrangler.jsonc, worker/ shim, migration notes). --dry-run prints instead of writing, --out-dir redirects output, --force overwrites existing wrangler config. |
cfify deploy [path] |
Emit wrangler config if missing, then stream npx wrangler deploy in the repo. |
Every command takes a [path] positional (default .), --format json|yaml|md, and --out <file>. JSON is the default — pipe it. plan defaults to md. emit/deploy write files, the rest are read-only.
| Your dep | Becomes | How |
|---|---|---|
express |
Worker fetch handler |
httpServerHandler(app) shim via nodejs_compat |
ioredis / redis |
KV namespace | env.CACHE.get/set binding + codemod notes |
multer / formidable / @aws-sdk/client-s3 |
R2 bucket | request.formData() → env.UPLOADS.put() |
pg / postgres / mysql2 |
Hyperdrive | env.DB binding, keeps your existing DB |
node-cron / cron / @nestjs/schedule |
Cron Trigger | scheduled() handler + triggers.crons |
ws / socket.io |
Durable Objects | SocketHub DO class + migrations scaffold |
bullmq / agenda / bull |
Cloudflare Queues | JOBS producer/consumer bindings |
nodemailer |
Email Workers | send_email binding |
jsonwebtoken |
WebCrypto / jose |
flagged — Node crypto doesn't exist on Workers |
mongoose |
manual | flagged error — no Workers-native Mongo path |
| Tool | What it does | Where cfify differs |
|---|---|---|
wrangler / wrangler init |
Scaffold a blank worker, run deploys | cfify reads your app — detects deps, emits bindings for services you actually use, keeps the wrangler CLI for the deploy step |
cf-workerify-style codemods |
Rewrite code only | cfify emits config + shim + notes, and scans first so you see blockers before touching code |
cf-ready / audit scripts |
Report-only checklist | cfify writes the files — emit + deploy take you to a live worker, not a report |
cfify doesn't replace wrangler — it generates wrangler's input.
Ships with three; the registry is overwrite-by-name so you can hot-patch or add your own.
| Adapter | Detects | Emits |
|---|---|---|
express |
express dep |
wrangler.jsonc + worker/ shim with httpServerHandler, service bindings, cron/DO/queue scaffolds |
nextjs |
next dep / next.config.* |
OpenNext-style plan + emit stubs (in progress) |
vite-spa |
vite dep w/o SSR |
static-assets worker + asset binding |
import type { Adapter } from 'cfify/types';
const myAdapter: Adapter = {
name: 'my-framework',
detect(repo) {
return repo.deps.has('my-framework') ? { confidence: 0.9 } : false;
},
async scan(repo) {
// return { framework, confidence, services, findings }
},
async emit(repo) {
// return { files: [{ path, content }], warnings }
},
};Register it:
import registry from 'cfify/adapters';
registry.register(myAdapter);detect is cheap and sync — check package.json deps and config files only. scan is thorough: reads sources, flags non-portable deps, scores confidence. emit returns file objects; cfify handles dry-run, --out-dir, and overwrite protection.
Third-party npm packages register adapters at CLI startup. cfify scans the repo's package.json deps/devDeps for packages named cfify-plugin-* or carrying the cfify.adapter keyword, plus anything in .cfifyrc.json:
{ "plugins": ["cfify-plugin-fastify", "./my-local-adapter.js"] }Each plugin module is dynamically imported. Contract — either:
export default function register(registry: AdapterRegistry) {
registry.register(myAdapter);
}
// and/or
export const adapters: Adapter[] = [myAdapter];Load failures warn on stderr and never block the CLI. cfify adapters marks each adapter builtin or plugin (<package>).
// package.json
{
"name": "cfify-plugin-fastify",
"version": "1.0.0",
"type": "module",
"main": "./index.js",
"keywords": ["cfify.adapter"], // optional when name starts cfify-plugin-
"peerDependencies": { "cfify": "*" }
}// index.ts
import type { Adapter, RepoContext } from 'cfify';
const fastify: Adapter = {
name: 'fastify',
detect: (repo: RepoContext) => (repo.depVersion('fastify') ? { confidence: 0.9 } : false),
scan: async (repo) => ({
framework: 'fastify',
version: repo.depVersion('fastify') ?? null,
confidence: 0.9,
services: [],
blockers: [],
findings: [],
}),
};
export const adapters = [fastify];npm i -D cfify-plugin-fastify in the target repo → cfify adapters lists it as plugin (cfify-plugin-fastify). Working example: cfify-plugin-example.
- Repo scanner (framework, PM, dep → service map)
- Express adapter: emit wrangler.jsonc + worker shim + migration notes
- KV / R2 / Hyperdrive / Cron / DO / Queues / Email binding generation
-
plan+doctorcommands - jscodeshift codemods for ioredis → KV, multer → R2 (in progress)
- Next.js adapter full emit (OpenNext path)
-
cfify deployprovisions resources (KV namespaces, R2 buckets) via wrangler before deploy - Fastify / Hono / NestJS adapters
-
cfify doctor --fix(install wrangler, wrangle auth)
MIT
{ "name": "express-api", "main": "worker/index.ts", "compatibility_flags": ["nodejs_compat"], "kv_namespaces": [{ "binding": "CACHE", "id": "REPLACE_ME" }], "r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "express-api-uploads" }], "hyperdrive": [{ "binding": "DB", "id": "REPLACE_ME" }], "triggers": { "crons": ["0 * * * *"] } }