Skip to content

Commit 1fecf38

Browse files
committed
Enhance Vercel Runtime Support
- Updated RuntimeSchema to include "vercel-edge" and "vercel-nodejs". - Added validation checks for Vercel runtimes requiring Hono backend in processAndValidateFlags. - Implemented setup functions for Vercel Edge and Node.js runtimes. - Updated project generation templates to support Vercel runtimes. - Adjusted database connection handling to use environment variables consistently. - Enhanced stack compatibility analysis to enforce Hono backend for Vercel runtimes. This commit improves the integration and validation of Vercel runtimes within the CLI.
1 parent 6499f8c commit 1fecf38

File tree

20 files changed

+245
-49
lines changed

20 files changed

+245
-49
lines changed

apps/cli/src/helpers/project-generation/template-manager.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,4 +831,36 @@ export async function handleExtras(
831831
);
832832
}
833833
}
834+
835+
if (context.runtime === "vercel-edge") {
836+
const runtimeVercelEdgeDir = path.join(
837+
PKG_ROOT,
838+
"templates/runtime/vercel-edge",
839+
);
840+
if (await fs.pathExists(runtimeVercelEdgeDir)) {
841+
await processAndCopyFiles(
842+
"**/*",
843+
runtimeVercelEdgeDir,
844+
projectDir,
845+
context,
846+
false,
847+
);
848+
}
849+
}
850+
851+
if (context.runtime === "vercel-nodejs") {
852+
const runtimeVercelNodejsDir = path.join(
853+
PKG_ROOT,
854+
"templates/runtime/vercel-nodejs",
855+
);
856+
if (await fs.pathExists(runtimeVercelNodejsDir)) {
857+
await processAndCopyFiles(
858+
"**/*",
859+
runtimeVercelNodejsDir,
860+
projectDir,
861+
context,
862+
false,
863+
);
864+
}
865+
}
834866
}

apps/cli/src/helpers/setup/backend-setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function setupBackendDependencies(
2424
dependencies.push("@hono/trpc-server");
2525
}
2626

27-
if (runtime === "node") {
27+
if (runtime === "node" || runtime === "vercel-nodejs") {
2828
dependencies.push("@hono/node-server");
2929
devDependencies.push("tsx", "@types/node");
3030
}

apps/cli/src/helpers/setup/runtime-setup.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export async function setupRuntime(config: ProjectConfig): Promise<void> {
2525
await setupNodeRuntime(serverDir, backend);
2626
} else if (runtime === "workers") {
2727
await setupWorkersRuntime(serverDir);
28+
} else if (runtime === "vercel-edge") {
29+
await setupVercelEdgeRuntime(serverDir);
30+
} else if (runtime === "vercel-nodejs") {
31+
await setupVercelNodejsRuntime(serverDir);
2832
}
2933
}
3034

@@ -145,3 +149,40 @@ async function setupWorkersRuntime(serverDir: string): Promise<void> {
145149
projectDir: serverDir,
146150
});
147151
}
152+
153+
async function setupVercelEdgeRuntime(serverDir: string): Promise<void> {
154+
const packageJsonPath = path.join(serverDir, "package.json");
155+
if (!(await fs.pathExists(packageJsonPath))) return;
156+
157+
const packageJson = await fs.readJson(packageJsonPath);
158+
159+
packageJson.scripts = {
160+
...packageJson.scripts,
161+
dev: "next dev",
162+
build: "next build",
163+
start: "next start",
164+
};
165+
166+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
167+
}
168+
169+
async function setupVercelNodejsRuntime(serverDir: string): Promise<void> {
170+
const packageJsonPath = path.join(serverDir, "package.json");
171+
if (!(await fs.pathExists(packageJsonPath))) return;
172+
173+
const packageJson = await fs.readJson(packageJsonPath);
174+
175+
packageJson.scripts = {
176+
...packageJson.scripts,
177+
dev: "next dev",
178+
build: "next build",
179+
start: "next start",
180+
};
181+
182+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
183+
184+
await addPackageDependency({
185+
dependencies: ["@hono/node-server"],
186+
projectDir: serverDir,
187+
});
188+
}

apps/cli/src/prompts/runtime.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ export async function getRuntimeChoice(
4040
label: "Cloudflare Workers (beta)",
4141
hint: "Edge runtime on Cloudflare's global network",
4242
});
43+
runtimeOptions.push({
44+
value: "vercel-edge",
45+
label: "Vercel Edge Runtime (beta)",
46+
hint: "Edge runtime on Vercel's global network",
47+
});
48+
runtimeOptions.push({
49+
value: "vercel-nodejs",
50+
label: "Vercel Node.js Runtime (beta)",
51+
hint: "Node.js runtime optimized for Vercel",
52+
});
4353
}
4454

4555
const response = await select<Runtime>({

apps/cli/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ export const BackendSchema = z
1616
export type Backend = z.infer<typeof BackendSchema>;
1717

1818
export const RuntimeSchema = z
19-
.enum(["bun", "node", "workers", "none"])
19+
.enum(["bun", "node", "workers", "vercel-edge", "vercel-nodejs", "none"])
2020
.describe(
21-
"Runtime environment (workers only available with hono backend and drizzle orm)",
21+
"Runtime environment (workers, vercel-edge, and vercel-nodejs only available with hono backend and drizzle orm)",
2222
);
2323
export type Runtime = z.infer<typeof RuntimeSchema>;
2424

apps/cli/src/validation.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,19 @@ export function processAndValidateFlags(
386386
process.exit(1);
387387
}
388388

389+
if (
390+
providedFlags.has("runtime") &&
391+
(options.runtime === "vercel-edge" ||
392+
options.runtime === "vercel-nodejs") &&
393+
config.backend &&
394+
config.backend !== "hono"
395+
) {
396+
consola.fatal(
397+
`Vercel runtime (--runtime ${options.runtime}) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`,
398+
);
399+
process.exit(1);
400+
}
401+
389402
if (
390403
providedFlags.has("backend") &&
391404
config.backend &&
@@ -398,6 +411,18 @@ export function processAndValidateFlags(
398411
process.exit(1);
399412
}
400413

414+
if (
415+
providedFlags.has("backend") &&
416+
config.backend &&
417+
config.backend !== "hono" &&
418+
(config.runtime === "vercel-edge" || config.runtime === "vercel-nodejs")
419+
) {
420+
consola.fatal(
421+
`Backend '${config.backend}' is not compatible with Vercel runtime. Vercel runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`,
422+
);
423+
process.exit(1);
424+
}
425+
401426
if (
402427
providedFlags.has("runtime") &&
403428
options.runtime === "workers" &&
@@ -588,6 +613,17 @@ export function validateConfigCompatibility(
588613
process.exit(1);
589614
}
590615
}
616+
617+
if (
618+
(effectiveRuntime === "vercel-edge" ||
619+
effectiveRuntime === "vercel-nodejs") &&
620+
effectiveBackend !== "hono"
621+
) {
622+
consola.fatal(
623+
`Vercel runtime is only supported with Hono backend. Current backend: ${effectiveBackend}. Please use a different runtime or change to Hono backend.`,
624+
);
625+
process.exit(1);
626+
}
591627
}
592628

593629
export function getProvidedFlags(options: CLIInput): Set<string> {

apps/cli/templates/backend/server/elysia/src/index.ts.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "dotenv/config";
1+
import env from "./env";
22
{{#if (eq runtime "node")}}
33
import { node } from "@elysiajs/node";
44
{{/if}}
@@ -29,7 +29,7 @@ const app = new Elysia()
2929
{{/if}}
3030
.use(
3131
cors({
32-
origin: process.env.CORS_ORIGIN || "",
32+
origin: env.CORS_ORIGIN || "",
3333
methods: ["GET", "POST", "OPTIONS"],
3434
{{#if auth}}
3535
allowedHeaders: ["Content-Type", "Authorization"],

apps/cli/templates/backend/server/express/src/index.ts.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "dotenv/config";
1+
import env from "./env";
22
{{#if (eq api "trpc")}}
33
import { createExpressMiddleware } from "@trpc/server/adapters/express";
44
import { createContext } from "./lib/context";
@@ -26,7 +26,7 @@ const app = express();
2626

2727
app.use(
2828
cors({
29-
origin: process.env.CORS_ORIGIN || "",
29+
origin: env.CORS_ORIGIN || "",
3030
methods: ["GET", "POST", "OPTIONS"],
3131
{{#if auth}}
3232
allowedHeaders: ["Content-Type", "Authorization"],

apps/cli/templates/backend/server/fastify/src/index.ts.hbs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "dotenv/config";
1+
import env from "./env";
22
import Fastify from "fastify";
33
import fastifyCors from "@fastify/cors";
44

@@ -29,7 +29,7 @@ import { auth } from "./lib/auth";
2929
{{/if}}
3030

3131
const baseCorsConfig = {
32-
origin: process.env.CORS_ORIGIN || "",
32+
origin: env.CORS_ORIGIN || "",
3333
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
3434
allowedHeaders: [
3535
"Content-Type",
@@ -44,7 +44,7 @@ const baseCorsConfig = {
4444
const handler = new RPCHandler(appRouter, {
4545
plugins: [
4646
new CORSPlugin({
47-
origin: process.env.CORS_ORIGIN,
47+
origin: env.CORS_ORIGIN,
4848
credentials: true,
4949
allowHeaders: ["Content-Type", "Authorization"],
5050
}),

apps/cli/templates/backend/server/hono/src/index.ts.hbs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
{{#if (or (eq runtime "bun") (eq runtime "node"))}}
2-
import "dotenv/config";
1+
{{#if (or (eq runtime "bun") (eq runtime "node") (eq runtime "vercel-edge") (eq runtime "vercel-nodejs"))}}
2+
import env from "./env";
33
{{/if}}
44
{{#if (eq runtime "workers")}}
55
import { env } from "cloudflare:workers";
@@ -20,7 +20,7 @@ import { auth } from "./lib/auth";
2020
import { Hono } from "hono";
2121
import { cors } from "hono/cors";
2222
import { logger } from "hono/logger";
23-
{{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node")))}}
23+
{{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node") (eq runtime "vercel-edge") (eq runtime "vercel-nodejs")))}}
2424
import { streamText } from "ai";
2525
import { google } from "@ai-sdk/google";
2626
import { stream } from "hono/streaming";
@@ -35,8 +35,8 @@ const app = new Hono();
3535

3636
app.use(logger());
3737
app.use("/*", cors({
38-
{{#if (or (eq runtime "bun") (eq runtime "node"))}}
39-
origin: process.env.CORS_ORIGIN || "",
38+
{{#if (or (eq runtime "bun") (eq runtime "node") (eq runtime "vercel-edge") (eq runtime "vercel-nodejs"))}}
39+
origin: env.CORS_ORIGIN || "",
4040
{{/if}}
4141
{{#if (eq runtime "workers")}}
4242
origin: env.CORS_ORIGIN || "",
@@ -77,7 +77,7 @@ app.use("/trpc/*", trpcServer({
7777
}));
7878
{{/if}}
7979

80-
{{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node")))}}
80+
{{#if (and (includes examples "ai") (or (eq runtime "bun") (eq runtime "node") (eq runtime "vercel-edge") (eq runtime "vercel-nodejs")))}}
8181
app.post("/ai", async (c) => {
8282
const body = await c.req.json();
8383
const messages = body.messages || [];
@@ -128,6 +128,12 @@ serve({
128128
export default app;
129129
{{/if}}
130130
{{#if (eq runtime "workers")}}
131+
export default app;
132+
{{/if}}
133+
{{#if (eq runtime "vercel-edge")}}
134+
export default app;
135+
{{/if}}
136+
{{#if (eq runtime "vercel-nodejs")}}
131137
export default app;
132138
{{/if}}
133139
{{/if}}

0 commit comments

Comments
 (0)