Skip to content

Commit 3baa412

Browse files
authored
feat: cli init git / select pkg mgr (#34)
1 parent f88ebe0 commit 3baa412

File tree

2 files changed

+130
-2
lines changed

2 files changed

+130
-2
lines changed

cli/src/index.ts

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
extractD1DatabaseId,
1010
extractHyperdriveId,
1111
extractKvNamespaceId,
12+
initializeGitRepository,
1213
parseWranglerToml,
1314
updateD1BlockWithId,
1415
updateHyperdriveBlockWithId,
@@ -78,6 +79,7 @@ interface GenerateAnswers {
7879
appName: string;
7980
template: "hono" | "nextjs";
8081
database: DbKind;
82+
packageManager: PackageManager;
8183
// D1
8284
d1Name?: string;
8385
d1Binding?: string;
@@ -182,6 +184,34 @@ function detectPackageManagerForAuth(cwd: string): PackageManager {
182184
return detectPackageManager(cwd);
183185
}
184186

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+
185215
function runScript(pm: PackageManager, script: string, cwd: string) {
186216
const args = pm === "bun" ? ["run", script] : pm === "yarn" ? [script] : ["run", script];
187217
return bunSpawnSync(pm, args, cwd);
@@ -568,6 +598,16 @@ function validateCliArgs(args: CliArgs): string[] {
568598
errors.push("database must be 'd1', 'hyperdrive-postgres', or 'hyperdrive-mysql'");
569599
}
570600

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+
571611
// Validate binding names
572612
const bindingFields = ["d1-binding", "hd-binding", "kv-binding", "r2-binding"];
573613
for (const field of bindingFields) {
@@ -607,6 +647,7 @@ function cliArgsToAnswers(args: CliArgs): Partial<GenerateAnswers> {
607647
if (args["app-name"]) answers.appName = args["app-name"] as string;
608648
if (args.template) answers.template = args.template as "hono" | "nextjs";
609649
if (args.database) answers.database = args.database as DbKind;
650+
if (args["package-manager"]) answers.packageManager = args["package-manager"] as PackageManager;
610651

611652
// D1 fields
612653
if (args["d1-name"]) answers.d1Name = args["d1-name"] as string;
@@ -867,10 +908,16 @@ async function generate(cliArgs?: CliArgs) {
867908
const partialAnswers = cliArgsToAnswers(cliArgs);
868909

869910
// 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+
870916
answers = {
871917
appName: partialAnswers.appName || "my-app",
872918
template: partialAnswers.template || "hono",
873919
database: partialAnswers.database || "d1",
920+
packageManager: defaultPackageManager,
874921
geolocation: partialAnswers.geolocation !== undefined ? partialAnswers.geolocation : true,
875922
kv: partialAnswers.kv !== undefined ? partialAnswers.kv : true,
876923
r2: partialAnswers.r2 !== undefined ? partialAnswers.r2 : false,
@@ -938,6 +985,23 @@ async function generate(cliArgs?: CliArgs) {
938985
initialValue: "d1",
939986
}) as Promise<DbKind>;
940987
},
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+
},
9411005
d1Name: ({ results }: { results: Partial<GenerateAnswers> }) =>
9421006
results.database === "d1"
9431007
? (text({
@@ -1495,8 +1559,8 @@ export const verification = {} as any;`;
14951559
14961560
// Install dependencies before Cloudflare resource creation
14971561
// 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}`);
15001564
let doInstall: boolean;
15011565
15021566
if (isNonInteractive) {
@@ -2041,6 +2105,19 @@ export const verification = {} as any;`;
20412105
}
20422106
}
20432107

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+
20442121
// Final instructions
20452122
const pmDev = pm === "yarn" ? "yarn dev" : pm === "npm" ? "npm run dev" : `${pm} run dev`;
20462123
const runScriptHelp = (name: string) =>
@@ -2094,6 +2171,7 @@ function printHelp() {
20942171
` --app-name=<name> Project name (default: my-app)\n` +
20952172
` --template=<template> hono | nextjs (default: hono)\n` +
20962173
` --database=<db> d1 | hyperdrive-postgres | hyperdrive-mysql (default: d1)\n` +
2174+
` --package-manager=<pm> pnpm | bun | yarn | npm (default: first available)\n` +
20972175
` --geolocation=<bool> Enable geolocation tracking (default: true)\n` +
20982176
` --kv=<bool> Use KV as secondary storage for Better Auth (default: true)\n` +
20992177
` --r2=<bool> Enable R2 to extend Better Auth with user file storage (default: false)\n` +

cli/src/lib/helpers.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,53 @@ export function updateHyperdriveBlockWithId(
317317

318318
return toml.slice(0, found.start) + block + toml.slice(found.end);
319319
}
320+
321+
export interface GitInitResult {
322+
success: boolean;
323+
error?: string;
324+
}
325+
326+
export function initializeGitRepository(projectPath: string): GitInitResult {
327+
const { spawnSync } = require("child_process") as typeof import("child_process");
328+
329+
// Check if git is available
330+
const gitCheck = spawnSync("git", ["--version"], { stdio: "pipe", encoding: "utf8" });
331+
if (gitCheck.status !== 0) {
332+
return { success: false, error: "Git is not installed or not available in PATH" };
333+
}
334+
335+
// Initialize git repository
336+
const initResult = spawnSync("git", ["init"], {
337+
cwd: projectPath,
338+
stdio: "pipe",
339+
encoding: "utf8",
340+
});
341+
342+
if (initResult.status !== 0) {
343+
return { success: false, error: `Failed to initialize git repository: ${initResult.stderr}` };
344+
}
345+
346+
// Add all files to staging
347+
const addResult = spawnSync("git", ["add", "."], {
348+
cwd: projectPath,
349+
stdio: "pipe",
350+
encoding: "utf8",
351+
});
352+
353+
if (addResult.status !== 0) {
354+
return { success: false, error: `Failed to add files to git: ${addResult.stderr}` };
355+
}
356+
357+
// Create initial commit
358+
const commitResult = spawnSync("git", ["commit", "-m", "Initial commit"], {
359+
cwd: projectPath,
360+
stdio: "pipe",
361+
encoding: "utf8",
362+
});
363+
364+
if (commitResult.status !== 0) {
365+
return { success: false, error: `Failed to create initial commit: ${commitResult.stderr}` };
366+
}
367+
368+
return { success: true };
369+
}

0 commit comments

Comments
 (0)