Skip to content

Commit b34e94a

Browse files
add cloudflare workers support (#326)
1 parent 5fc1ba1 commit b34e94a

File tree

34 files changed

+555
-537
lines changed

34 files changed

+555
-537
lines changed

.changeset/proud-seals-admire.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-better-t-stack": minor
3+
---
4+
5+
add cloudflare workers support for hono

apps/cli/src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ export const dependencyVersionMap = {
105105

106106
"@tanstack/solid-query": "^5.75.0",
107107
"@tanstack/solid-query-devtools": "^5.75.0",
108+
109+
wrangler: "^4.20.0",
108110
} as const;
109111

110112
export type AvailableDependencies = keyof typeof dependencyVersionMap;

apps/cli/src/helpers/project-generation/env-setup.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ export async function setupEnvironmentVariables(
200200
databaseUrl = "mongodb://localhost:27017/mydatabase";
201201
break;
202202
case "sqlite":
203-
databaseUrl = "file:./local.db";
203+
if (config.runtime === "workers") {
204+
databaseUrl = "http://127.0.0.1:8080";
205+
} else {
206+
databaseUrl = "file:./local.db";
207+
}
204208
break;
205209
}
206210
}
@@ -234,4 +238,11 @@ export async function setupEnvironmentVariables(
234238
];
235239

236240
await addEnvVariablesToFile(envPath, serverVars);
241+
242+
if (config.runtime === "workers") {
243+
const devVarsPath = path.join(serverDir, ".dev.vars");
244+
try {
245+
await fs.copy(envPath, devVarsPath);
246+
} catch (_err) {}
247+
}
237248
}

apps/cli/src/helpers/project-generation/post-installation.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,18 @@ export function displayPostInstallInstructions(
9393
)}\n`;
9494
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
9595
} else {
96-
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
96+
if (runtime !== "workers") {
97+
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
98+
}
99+
100+
if (runtime === "workers") {
101+
output += `${pc.cyan(`${stepCounter++}.`)} bun dev\n`;
102+
output += `${pc.cyan(
103+
`${stepCounter++}.`,
104+
)} cd apps/server && bun run cf-typegen\n\n`;
105+
} else {
106+
output += "\n";
107+
}
97108
}
98109

99110
output += `${pc.bold("Your project will be available at:")}\n`;

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,4 +818,17 @@ export async function handleExtras(
818818
await processTemplate(npmrcTemplateSrc, npmrcDest, context);
819819
}
820820
}
821+
822+
if (context.runtime === "workers") {
823+
const runtimeWorkersDir = path.join(PKG_ROOT, "templates/runtime/workers");
824+
if (await fs.pathExists(runtimeWorkersDir)) {
825+
await processAndCopyFiles(
826+
"**/*",
827+
runtimeWorkersDir,
828+
projectDir,
829+
context,
830+
false,
831+
);
832+
}
833+
}
821834
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export async function setupRuntime(config: ProjectConfig): Promise<void> {
2020
await setupBunRuntime(serverDir, backend);
2121
} else if (runtime === "node") {
2222
await setupNodeRuntime(serverDir, backend);
23+
} else if (runtime === "workers") {
24+
await setupWorkersRuntime(serverDir);
2325
}
2426
}
2527

@@ -80,3 +82,26 @@ async function setupNodeRuntime(
8082
});
8183
}
8284
}
85+
86+
async function setupWorkersRuntime(serverDir: string): Promise<void> {
87+
const packageJsonPath = path.join(serverDir, "package.json");
88+
if (!(await fs.pathExists(packageJsonPath))) return;
89+
90+
const packageJson = await fs.readJson(packageJsonPath);
91+
92+
packageJson.scripts = {
93+
...packageJson.scripts,
94+
dev: "wrangler dev --port=3000",
95+
start: "wrangler dev",
96+
deploy: "wrangler deploy",
97+
build: "wrangler deploy --dry-run",
98+
"cf-typegen": "wrangler types --env-interface CloudflareBindings",
99+
};
100+
101+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
102+
103+
await addPackageDependency({
104+
devDependencies: ["wrangler"],
105+
projectDir: serverDir,
106+
});
107+
}

apps/cli/src/prompts/config-prompts.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@ export async function gatherConfig(
5757
runtime: ({ results }) =>
5858
getRuntimeChoice(flags.runtime, results.backend),
5959
database: ({ results }) =>
60-
getDatabaseChoice(flags.database, results.backend),
60+
getDatabaseChoice(flags.database, results.backend, results.runtime),
6161
orm: ({ results }) =>
6262
getORMChoice(
6363
flags.orm,
6464
results.database !== "none",
6565
results.database,
6666
results.backend,
67+
results.runtime,
6768
),
6869
api: ({ results }) =>
6970
getApiChoice(flags.api, results.frontend, results.backend),

apps/cli/src/prompts/database.ts

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,57 @@
11
import { cancel, isCancel, select } from "@clack/prompts";
22
import pc from "picocolors";
33
import { DEFAULT_CONFIG } from "../constants";
4-
import type { Backend, Database } from "../types";
4+
import type { Backend, Database, Runtime } from "../types";
55

66
export async function getDatabaseChoice(
77
database?: Database,
88
backend?: Backend,
9+
runtime?: Runtime,
910
): Promise<Database> {
1011
if (backend === "convex" || backend === "none") {
1112
return "none";
1213
}
1314

1415
if (database !== undefined) return database;
1516

17+
const databaseOptions: Array<{
18+
value: Database;
19+
label: string;
20+
hint: string;
21+
}> = [
22+
{
23+
value: "none",
24+
label: "None",
25+
hint: "No database setup",
26+
},
27+
{
28+
value: "sqlite",
29+
label: "SQLite",
30+
hint: "lightweight, server-less, embedded relational database",
31+
},
32+
{
33+
value: "postgres",
34+
label: "PostgreSQL",
35+
hint: "powerful, open source object-relational database system",
36+
},
37+
{
38+
value: "mysql",
39+
label: "MySQL",
40+
hint: "popular open-source relational database system",
41+
},
42+
];
43+
44+
if (runtime !== "workers") {
45+
databaseOptions.push({
46+
value: "mongodb",
47+
label: "MongoDB",
48+
hint: "open-source NoSQL database that stores data in JSON-like documents called BSON",
49+
});
50+
}
51+
1652
const response = await select<Database>({
1753
message: "Select database",
18-
options: [
19-
{
20-
value: "none",
21-
label: "None",
22-
hint: "No database setup",
23-
},
24-
{
25-
value: "sqlite",
26-
label: "SQLite",
27-
hint: "lightweight, server-less, embedded relational database",
28-
},
29-
{
30-
value: "postgres",
31-
label: "PostgreSQL",
32-
hint: "powerful, open source object-relational database system",
33-
},
34-
{
35-
value: "mysql",
36-
label: "MySQL",
37-
hint: "popular open-source relational database system",
38-
},
39-
{
40-
value: "mongodb",
41-
label: "MongoDB",
42-
hint: "open-source NoSQL database that stores data in JSON-like documents called BSON",
43-
},
44-
],
54+
options: databaseOptions,
4555
initialValue: DEFAULT_CONFIG.database,
4656
});
4757

apps/cli/src/prompts/orm.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { cancel, isCancel, select } from "@clack/prompts";
22
import pc from "picocolors";
33
import { DEFAULT_CONFIG } from "../constants";
4-
import type { Backend, Database, ORM } from "../types";
4+
import type { Backend, Database, ORM, Runtime } from "../types";
55

66
const ormOptions = {
77
prisma: {
@@ -26,6 +26,7 @@ export async function getORMChoice(
2626
hasDatabase: boolean,
2727
database?: Database,
2828
backend?: Backend,
29+
runtime?: Runtime,
2930
): Promise<ORM> {
3031
if (backend === "convex") {
3132
return "none";
@@ -34,6 +35,10 @@ export async function getORMChoice(
3435
if (!hasDatabase) return "none";
3536
if (orm !== undefined) return orm;
3637

38+
if (runtime === "workers") {
39+
return "drizzle";
40+
}
41+
3742
const options = [
3843
...(database === "mongodb"
3944
? [ormOptions.prisma, ormOptions.mongoose]

apps/cli/src/prompts/runtime.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,34 @@ export async function getRuntimeChoice(
1717
return "node";
1818
}
1919

20+
const runtimeOptions: Array<{
21+
value: Runtime;
22+
label: string;
23+
hint: string;
24+
}> = [
25+
{
26+
value: "bun",
27+
label: "Bun",
28+
hint: "Fast all-in-one JavaScript runtime",
29+
},
30+
{
31+
value: "node",
32+
label: "Node.js",
33+
hint: "Traditional Node.js runtime",
34+
},
35+
];
36+
37+
if (backend === "hono") {
38+
runtimeOptions.push({
39+
value: "workers",
40+
label: "Cloudflare Workers (beta)",
41+
hint: "Edge runtime on Cloudflare's global network",
42+
});
43+
}
44+
2045
const response = await select<Runtime>({
2146
message: "Select runtime",
22-
options: [
23-
{
24-
value: "bun",
25-
label: "Bun",
26-
hint: "Fast all-in-one JavaScript runtime",
27-
},
28-
{
29-
value: "node",
30-
label: "Node.js",
31-
hint: "Traditional Node.js runtime",
32-
},
33-
],
47+
options: runtimeOptions,
3448
initialValue: DEFAULT_CONFIG.runtime,
3549
});
3650

0 commit comments

Comments
 (0)