princepal9120/cfify

Convert TypeScript apps to Cloudflare Workers — framework detection, service codemods, wrangler emit

★ 0Forks 0TypeScriptGitHub ↗Compare

README

cfify

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.

Install

npm i -g cfify
# or run once, no install:
npx cfify .

30-second demo

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:

{
  "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 * * * *"] }
}

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.).

Commands

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.

What cfify translates

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

vs. the alternatives

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.

Adapters

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

Writing an adapter

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.

Adapter plugins

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>).

Writing a plugin — cfify-plugin-fastify

// 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.

Roadmap

  • 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 + doctor commands
  • jscodeshift codemods for ioredis → KV, multer → R2 (in progress)
  • Next.js adapter full emit (OpenNext path)
  • cfify deploy provisions resources (KV namespaces, R2 buckets) via wrangler before deploy
  • Fastify / Hono / NestJS adapters
  • cfify doctor --fix (install wrangler, wrangle auth)

License

MIT

Contributors

princepal9120

Issues