diff --git a/.changeset/spotty-candles-draw.md b/.changeset/spotty-candles-draw.md new file mode 100644 index 0000000..dd83c9d --- /dev/null +++ b/.changeset/spotty-candles-draw.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +feat: add package manager selection and configuration diff --git a/apps/cli/package.json b/apps/cli/package.json index fe09d2c..3c28390 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -21,6 +21,7 @@ "@inquirer/prompts": "^7.3.1", "chalk": "^5.3.0", "commander": "^13.1.0", + "detect-package-manager": "^3.0.2", "execa": "^8.0.1", "fs-extra": "^11.2.0", "gradient-string": "^3.0.0", diff --git a/apps/cli/src/consts.ts b/apps/cli/src/consts.ts index 90b01f1..05f1b5a 100644 --- a/apps/cli/src/consts.ts +++ b/apps/cli/src/consts.ts @@ -34,4 +34,5 @@ export const DEFAULT_CONFIG: ProjectConfig = { auth: true, features: [], git: true, + packageManager: "npm", }; diff --git a/apps/cli/src/create-project.ts b/apps/cli/src/create-project.ts index a5517c7..ba1cd52 100644 --- a/apps/cli/src/create-project.ts +++ b/apps/cli/src/create-project.ts @@ -1,9 +1,11 @@ import path from "node:path"; +import { confirm, select } from "@inquirer/prompts"; +import { detect } from "detect-package-manager"; import { execa } from "execa"; import fs from "fs-extra"; import ora from "ora"; import { setupTurso } from "./helpers/db-setup"; -import type { ProjectConfig } from "./types"; +import type { PackageManager, ProjectConfig } from "./types"; import { logger } from "./utils/logger"; export async function createProject(options: ProjectConfig) { @@ -22,15 +24,67 @@ export async function createProject(options: ProjectConfig) { ]); spinner.succeed(); - if (options.git) { + const initGit = await confirm({ + message: "Initialize a git repository?", + default: true, + }); + + if (initGit) { spinner.start("Initializing git repository..."); await execa("git", ["init"], { cwd: projectDir }); spinner.succeed(); } - spinner.start("Installing dependencies..."); - await execa("bun", ["install"], { cwd: projectDir }); - spinner.succeed(); + let packageManager = options.packageManager; + + if (!packageManager) { + const detectedPackageManager = await detect(); + + const useDetectedPackageManager = await confirm({ + message: `Use detected package manager (${detectedPackageManager})?`, + default: true, + }); + + if (useDetectedPackageManager) { + packageManager = detectedPackageManager; + } else { + packageManager = await select({ + message: "Select package manager:", + choices: [ + { value: "npm", name: "npm" }, + { value: "yarn", name: "yarn" }, + { value: "pnpm", name: "pnpm" }, + { value: "bun", name: "bun" }, + ], + }); + } + } + + const installDeps = await confirm({ + message: `Install dependencies using ${packageManager}?`, + default: true, + }); + + if (installDeps) { + spinner.start(`Installing dependencies using ${packageManager}...`); + switch (packageManager) { + case "npm": + await execa("npm", ["install"], { cwd: projectDir }); + break; + case "yarn": + await execa("yarn", ["install"], { cwd: projectDir }); + break; + case "pnpm": + await execa("pnpm", ["install"], { cwd: projectDir }); + break; + case "bun": + await execa("bun", ["install"], { cwd: projectDir }); + break; + default: + throw new Error("Unsupported package manager"); + } + spinner.succeed(); + } if (options.database === "libsql") { await setupTurso(projectDir); @@ -39,7 +93,12 @@ export async function createProject(options: ProjectConfig) { logger.success("\n✨ Project created successfully!\n"); logger.info("Next steps:"); logger.info(` cd ${options.projectName}`); - logger.info(" bun dev"); + if (!installDeps) { + logger.info(` ${packageManager} install`); + } + logger.info( + ` ${packageManager === "npm" ? "npm run" : packageManager} dev`, + ); } catch (error) { spinner.fail("Failed to create project"); logger.error("Error during project creation:", error); diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index cef614d..127e2e8 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -4,7 +4,12 @@ import { Command } from "commander"; import { DEFAULT_CONFIG } from "./consts"; import { createProject } from "./create-project"; import { renderTitle } from "./render-title"; -import type { ProjectConfig, ProjectDatabase, ProjectFeature } from "./types"; +import type { + PackageManager, + ProjectConfig, + ProjectDatabase, + ProjectFeature, +} from "./types"; import { generateReproducibleCommand } from "./utils/generate-reproducible-command"; import { getVersion } from "./utils/get-version"; import { logger } from "./utils/logger"; @@ -103,6 +108,10 @@ async function main() { .option("--docker", "Include Docker setup") .option("--github-actions", "Include GitHub Actions") .option("--seo", "Include SEO setup") + .option( + "--package-manager ", + "Package manager to use (npm, yarn, pnpm, or bun)", + ) .parse(); const options = program.opts(); @@ -112,6 +121,7 @@ async function main() { projectName: projectDirectory, database: options.database as ProjectDatabase, auth: options.auth, + packageManager: options.packageManager as PackageManager, features: [ ...(options.docker ? ["docker"] : []), ...(options.githubActions ? ["github-actions"] : []), diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index 2b21eb9..57a64b3 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -7,5 +7,8 @@ export type ProjectConfig = { git: boolean; database: ProjectDatabase; auth: boolean; + packageManager?: PackageManager; features: ProjectFeature[]; }; + +export type PackageManager = "npm" | "yarn" | "pnpm" | "bun"; diff --git a/apps/cli/src/utils/generate-reproducible-command.ts b/apps/cli/src/utils/generate-reproducible-command.ts index 1f89bfc..b2a8815 100644 --- a/apps/cli/src/utils/generate-reproducible-command.ts +++ b/apps/cli/src/utils/generate-reproducible-command.ts @@ -2,29 +2,26 @@ import { DEFAULT_CONFIG } from "../consts"; import type { ProjectConfig } from "../types"; export function generateReproducibleCommand(config: ProjectConfig): string { - const parts = ["bunx create-better-t-stack"]; - - if (config.projectName !== DEFAULT_CONFIG.projectName) { - parts.push(config.projectName); - } + const flags: string[] = []; if (config.database !== DEFAULT_CONFIG.database) { - parts.push(`--database ${config.database}`); + flags.push(`--database ${config.database}`); } - if (config.auth !== DEFAULT_CONFIG.auth) { - parts.push(config.auth ? "--auth" : "--no-auth"); + flags.push("--no-auth"); + } + if ( + config.packageManager && + config.packageManager !== DEFAULT_CONFIG.packageManager + ) { + flags.push(`--package-manager ${config.packageManager}`); } - if (config.features.includes("docker")) { - parts.push("--docker"); - } - if (config.features.includes("github-actions")) { - parts.push("--github-actions"); - } - if (config.features.includes("SEO")) { - parts.push("--seo"); + for (const feature of config.features) { + flags.push(`--${feature}`); } - return parts.join(" "); + return `npx create-better-t-stack${ + config.projectName ? ` ${config.projectName}` : "" + }${flags.length > 0 ? ` ${flags.join(" ")}` : ""}`; } diff --git a/bun.lock b/bun.lock index c4e9da9..6a50a99 100644 --- a/bun.lock +++ b/bun.lock @@ -14,14 +14,15 @@ }, "apps/cli": { "name": "create-better-t-stack", - "version": "0.1.1", + "version": "0.2.1", "bin": { - "create-better-t-stack": "./dist/index.js" + "create-better-t-stack": "dist/index.js" }, "dependencies": { "@inquirer/prompts": "^7.3.1", "chalk": "^5.3.0", "commander": "^13.1.0", + "detect-package-manager": "^3.0.2", "execa": "^8.0.1", "fs-extra": "^11.2.0", "gradient-string": "^3.0.0", @@ -722,6 +723,8 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + "detect-package-manager": ["detect-package-manager@3.0.2", "", { "dependencies": { "execa": "^5.1.1" } }, "sha512-8JFjJHutStYrfWwzfretQoyNGoZVW1Fsrp4JO9spa7h/fBfwgTMEIy4/LBzRDGsxwVPHU0q+T9YvwLDJoOApLQ=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], @@ -1776,6 +1779,8 @@ "cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "detect-package-manager/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + "enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -1920,6 +1925,20 @@ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "detect-package-manager/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "detect-package-manager/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "detect-package-manager/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "detect-package-manager/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "detect-package-manager/execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "detect-package-manager/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "detect-package-manager/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + "enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "eslint/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -2004,6 +2023,8 @@ "@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "detect-package-manager/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], } }