|
9 | 9 | extractD1DatabaseId, |
10 | 10 | extractHyperdriveId, |
11 | 11 | extractKvNamespaceId, |
| 12 | + initializeGitRepository, |
12 | 13 | parseWranglerToml, |
13 | 14 | updateD1BlockWithId, |
14 | 15 | updateHyperdriveBlockWithId, |
@@ -78,6 +79,7 @@ interface GenerateAnswers { |
78 | 79 | appName: string; |
79 | 80 | template: "hono" | "nextjs"; |
80 | 81 | database: DbKind; |
| 82 | + packageManager: PackageManager; |
81 | 83 | // D1 |
82 | 84 | d1Name?: string; |
83 | 85 | d1Binding?: string; |
@@ -182,6 +184,34 @@ function detectPackageManagerForAuth(cwd: string): PackageManager { |
182 | 184 | return detectPackageManager(cwd); |
183 | 185 | } |
184 | 186 |
|
| 187 | +function getAvailablePackageManagers(): PackageManager[] { |
| 188 | + const available: PackageManager[] = []; |
| 189 | + const preferredOrder: PackageManager[] = ["pnpm", "npm", "bun", "yarn"]; |
| 190 | + |
| 191 | + for (const pm of preferredOrder) { |
| 192 | + if (commandAvailable(pm)) { |
| 193 | + available.push(pm); |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + return available; |
| 198 | +} |
| 199 | + |
| 200 | +function getPackageManagerDisplayName(pm: PackageManager): string { |
| 201 | + switch (pm) { |
| 202 | + case "bun": |
| 203 | + return "Bun"; |
| 204 | + case "pnpm": |
| 205 | + return "pnpm"; |
| 206 | + case "yarn": |
| 207 | + return "Yarn"; |
| 208 | + case "npm": |
| 209 | + return "npm"; |
| 210 | + default: |
| 211 | + return pm; |
| 212 | + } |
| 213 | +} |
| 214 | + |
185 | 215 | function runScript(pm: PackageManager, script: string, cwd: string) { |
186 | 216 | const args = pm === "bun" ? ["run", script] : pm === "yarn" ? [script] : ["run", script]; |
187 | 217 | return bunSpawnSync(pm, args, cwd); |
@@ -568,6 +598,16 @@ function validateCliArgs(args: CliArgs): string[] { |
568 | 598 | errors.push("database must be 'd1', 'hyperdrive-postgres', or 'hyperdrive-mysql'"); |
569 | 599 | } |
570 | 600 |
|
| 601 | + // Validate package manager |
| 602 | + if (args["package-manager"]) { |
| 603 | + const pm = args["package-manager"] as string; |
| 604 | + if (!["bun", "pnpm", "yarn", "npm"].includes(pm)) { |
| 605 | + errors.push("package-manager must be 'bun', 'pnpm', 'yarn', or 'npm'"); |
| 606 | + } else if (!commandAvailable(pm as PackageManager)) { |
| 607 | + errors.push(`package-manager '${pm}' is not available on this system`); |
| 608 | + } |
| 609 | + } |
| 610 | + |
571 | 611 | // Validate binding names |
572 | 612 | const bindingFields = ["d1-binding", "hd-binding", "kv-binding", "r2-binding"]; |
573 | 613 | for (const field of bindingFields) { |
@@ -607,6 +647,7 @@ function cliArgsToAnswers(args: CliArgs): Partial<GenerateAnswers> { |
607 | 647 | if (args["app-name"]) answers.appName = args["app-name"] as string; |
608 | 648 | if (args.template) answers.template = args.template as "hono" | "nextjs"; |
609 | 649 | if (args.database) answers.database = args.database as DbKind; |
| 650 | + if (args["package-manager"]) answers.packageManager = args["package-manager"] as PackageManager; |
610 | 651 |
|
611 | 652 | // D1 fields |
612 | 653 | if (args["d1-name"]) answers.d1Name = args["d1-name"] as string; |
@@ -867,10 +908,16 @@ async function generate(cliArgs?: CliArgs) { |
867 | 908 | const partialAnswers = cliArgsToAnswers(cliArgs); |
868 | 909 |
|
869 | 910 | // Fill in required fields with defaults if not provided |
| 911 | + const availablePackageManagers = getAvailablePackageManagers(); |
| 912 | + const defaultPackageManager = |
| 913 | + partialAnswers.packageManager || |
| 914 | + (availablePackageManagers.length > 0 ? availablePackageManagers[0] : "npm"); |
| 915 | + |
870 | 916 | answers = { |
871 | 917 | appName: partialAnswers.appName || "my-app", |
872 | 918 | template: partialAnswers.template || "hono", |
873 | 919 | database: partialAnswers.database || "d1", |
| 920 | + packageManager: defaultPackageManager, |
874 | 921 | geolocation: partialAnswers.geolocation !== undefined ? partialAnswers.geolocation : true, |
875 | 922 | kv: partialAnswers.kv !== undefined ? partialAnswers.kv : true, |
876 | 923 | r2: partialAnswers.r2 !== undefined ? partialAnswers.r2 : false, |
@@ -938,6 +985,23 @@ async function generate(cliArgs?: CliArgs) { |
938 | 985 | initialValue: "d1", |
939 | 986 | }) as Promise<DbKind>; |
940 | 987 | }, |
| 988 | + packageManager: () => { |
| 989 | + const available = getAvailablePackageManagers(); |
| 990 | + if (available.length === 0) { |
| 991 | + return Promise.resolve("npm" as PackageManager); |
| 992 | + } |
| 993 | + if (available.length === 1) { |
| 994 | + return Promise.resolve(available[0]); |
| 995 | + } |
| 996 | + return (select as any)({ |
| 997 | + message: "Package manager", |
| 998 | + options: available.map(pm => ({ |
| 999 | + value: pm, |
| 1000 | + label: getPackageManagerDisplayName(pm), |
| 1001 | + })), |
| 1002 | + initialValue: available[0], |
| 1003 | + }) as Promise<PackageManager>; |
| 1004 | + }, |
941 | 1005 | d1Name: ({ results }: { results: Partial<GenerateAnswers> }) => |
942 | 1006 | results.database === "d1" |
943 | 1007 | ? (text({ |
@@ -1495,8 +1559,8 @@ export const verification = {} as any;`; |
1495 | 1559 |
|
1496 | 1560 | // Install dependencies before Cloudflare resource creation |
1497 | 1561 | // This ensures projects are buildable even if Cloudflare setup fails |
1498 | | - const pm = detectPackageManager(targetDir); |
1499 | | - debugLog(`Detected package manager: ${pm}`); |
| 1562 | + const pm = answers.packageManager; |
| 1563 | + debugLog(`Using selected package manager: ${pm}`); |
1500 | 1564 | let doInstall: boolean; |
1501 | 1565 |
|
1502 | 1566 | if (isNonInteractive) { |
@@ -2041,6 +2105,19 @@ export const verification = {} as any;`; |
2041 | 2105 | } |
2042 | 2106 | } |
2043 | 2107 |
|
| 2108 | + // Initialize git repository |
| 2109 | + debugLog("Initializing git repository"); |
| 2110 | + const gitSpinner = spinner(); |
| 2111 | + gitSpinner.start("Initializing git repository..."); |
| 2112 | + |
| 2113 | + const gitResult = initializeGitRepository(targetDir); |
| 2114 | + if (gitResult.success) { |
| 2115 | + gitSpinner.stop(pc.green("Git repository initialized with initial commit")); |
| 2116 | + } else { |
| 2117 | + gitSpinner.stop(pc.yellow("Git initialization skipped")); |
| 2118 | + debugLog(`Git initialization failed: ${gitResult.error}`); |
| 2119 | + } |
| 2120 | + |
2044 | 2121 | // Final instructions |
2045 | 2122 | const pmDev = pm === "yarn" ? "yarn dev" : pm === "npm" ? "npm run dev" : `${pm} run dev`; |
2046 | 2123 | const runScriptHelp = (name: string) => |
@@ -2094,6 +2171,7 @@ function printHelp() { |
2094 | 2171 | ` --app-name=<name> Project name (default: my-app)\n` + |
2095 | 2172 | ` --template=<template> hono | nextjs (default: hono)\n` + |
2096 | 2173 | ` --database=<db> d1 | hyperdrive-postgres | hyperdrive-mysql (default: d1)\n` + |
| 2174 | + ` --package-manager=<pm> pnpm | bun | yarn | npm (default: first available)\n` + |
2097 | 2175 | ` --geolocation=<bool> Enable geolocation tracking (default: true)\n` + |
2098 | 2176 | ` --kv=<bool> Use KV as secondary storage for Better Auth (default: true)\n` + |
2099 | 2177 | ` --r2=<bool> Enable R2 to extend Better Auth with user file storage (default: false)\n` + |
|
0 commit comments